Merge "Don't count swiping up on the locked shade as a swipe to dismiss." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6b8baf8..4e34b63 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -172,6 +172,7 @@
// Window
aconfig_declarations {
name: "com.android.window.flags.window-aconfig",
+ exportable: true,
package: "com.android.window.flags",
container: "system",
srcs: ["core/java/android/window/flags/*.aconfig"],
@@ -478,6 +479,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "android.os.vibrator.flags-aconfig-cc",
+ aconfig_declarations: "android.os.vibrator.flags-aconfig",
+ host_supported: true,
+ vendor_available: true,
+}
+
// View
aconfig_declarations {
name: "android.view.flags-aconfig",
@@ -1101,6 +1109,7 @@
// Chooser / "Sharesheet"
aconfig_declarations {
name: "android.service.chooser.flags-aconfig",
+ exportable: true,
package: "android.service.chooser",
container: "system",
srcs: ["core/java/android/service/chooser/flags.aconfig"],
diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS
index 48a0201..02b0a1e 100644
--- a/PERFORMANCE_OWNERS
+++ b/PERFORMANCE_OWNERS
@@ -6,3 +6,4 @@
shayba@google.com
jdduke@google.com
shombert@google.com
+kevinjeon@google.com
diff --git a/Ravenwood.bp b/Ravenwood.bp
index cebeb18..5f32ba0 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -25,25 +25,16 @@
visibility: ["//visibility:private"],
}
-// Generate the stub/impl from framework-all, with hidden APIs.
-java_genrule {
- name: "framework-minus-apex.ravenwood-base",
+// Process framework-all with hoststubgen for Ravenwood.
+// This step takes several tens of seconds, so we manually shard it to multiple modules.
+// All the copies have to be kept in sync.
+// TODO: Do the sharding better, either by making hostsubgen support sharding natively, or
+// making a better build rule.
+
+genrule_defaults {
+ name: "framework-minus-apex.ravenwood-base_defaults",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
tools: ["hoststubgen"],
- cmd: "$(location hoststubgen) " +
- "@$(location :ravenwood-standard-options) " +
-
- "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
- "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
- "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
-
- "--out-impl-jar $(location ravenwood.jar) " +
-
- "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
- "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) " +
-
- "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
- "--policy-override-file $(location :ravenwood-framework-policies) " +
- "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
srcs: [
":framework-minus-apex-for-hoststubgen",
":ravenwood-framework-policies",
@@ -52,27 +43,120 @@
],
out: [
"ravenwood.jar",
+ "hoststubgen_framework-minus-apex.log",
+ ],
+}
- // Following files are created just as FYI.
+framework_minus_apex_cmd = "$(location hoststubgen) " +
+ "@$(location :ravenwood-standard-options) " +
+ "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
+ "--out-impl-jar $(location ravenwood.jar) " +
+ "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
+ "--policy-override-file $(location :ravenwood-framework-policies) " +
+ "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) "
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X0",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 0",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X1",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 1",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X2",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 2",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X3",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 3",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X4",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 4",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X5",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 5",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X6",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 6",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X7",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 7",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X8",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 8",
+}
+
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_X9",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 9",
+}
+
+// Build framework-minus-apex.ravenwood-base without sharding.
+// We extract the various dump files from this one, rather than the sharded ones, because
+// some dumps use the output from other classes (e.g. base classes) which may not be in the
+// same shard. Also some of the dump files ("apis") may be slow even when sharded, because
+// the output contains the information from all the input classes, rather than the output classes.
+// Not using sharding is fine for this module because it's only used for collecting the
+// dump / stats files, which don't have to happen regularly.
+java_genrule {
+ name: "framework-minus-apex.ravenwood-base_all",
+ defaults: ["framework-minus-apex.ravenwood-base_defaults"],
+ cmd: framework_minus_apex_cmd +
+ "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +
+ "--supported-api-list-file $(location hoststubgen_framework-minus-apex_apis.csv) " +
+
+ "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) ",
+
+ out: [
"hoststubgen_framework-minus-apex_keep_all.txt",
"hoststubgen_framework-minus-apex_dump.txt",
-
- "hoststubgen_framework-minus-apex.log",
"hoststubgen_framework-minus-apex_stats.csv",
"hoststubgen_framework-minus-apex_apis.csv",
],
- defaults: ["ravenwood-internal-only-visibility-genrule"],
}
-// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
-// Note this emits a "device side" output, so that ravenwood tests can (implicitly)
-// depend on it.
+// Marge all the sharded jars
java_genrule {
name: "framework-minus-apex.ravenwood",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
+ defaults: ["ravenwood-internal-only-visibility-java"],
+ cmd: "$(location merge_zips) $(out) $(in)",
+ tools: ["merge_zips"],
srcs: [
- ":framework-minus-apex.ravenwood-base{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X0{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X1{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X2{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X3{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X4{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X5{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X6{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X7{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X8{ravenwood.jar}",
+ ":framework-minus-apex.ravenwood-base_X9{ravenwood.jar}",
],
out: [
"framework-minus-apex.ravenwood.jar",
@@ -139,6 +223,9 @@
],
}
+// TODO(b/313930116) This jarjar is a bit slow. We should use hoststubgen for renaming,
+// but services.core.ravenwood has complex dependencies, so it'll take more than
+// just using hoststubgen "rename"s.
java_library {
name: "services.core.ravenwood-jarjar",
defaults: ["ravenwood-internal-only-visibility-java"],
@@ -151,7 +238,6 @@
// Jars in "ravenwood-runtime" are set to the classpath, sorted alphabetically.
// Rename some of the dependencies to make sure they're included in the intended order.
-// Also apply jarjar.
java_library {
name: "100-framework-minus-apex.ravenwood",
defaults: ["ravenwood-internal-only-visibility-java"],
diff --git a/ZYGOTE_OWNERS b/ZYGOTE_OWNERS
index f6d15e0..6918c16 100644
--- a/ZYGOTE_OWNERS
+++ b/ZYGOTE_OWNERS
@@ -1,4 +1,4 @@
chriswailes@google.com
+hboehm@google.com
maco@google.com
-narayan@google.com
ngeoffray@google.com
diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
index fbe67a4..c34936f 100644
--- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
@@ -19,6 +19,7 @@
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.RenderNode;
+import android.graphics.Typeface;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
@@ -120,13 +121,34 @@
public void testSetFontVariationSettings() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final Paint paint = new Paint(PAINT);
- final Random random = new Random(0);
while (state.keepRunning()) {
state.pauseTiming();
- int weight = random.nextInt(1000);
+ paint.setTypeface(null);
+ paint.setFontVariationSettings(null);
+ Typeface.clearTypefaceCachesForTestingPurpose();
state.resumeTiming();
- paint.setFontVariationSettings("'wght' " + weight);
+ paint.setFontVariationSettings("'wght' 450");
}
+ Typeface.clearTypefaceCachesForTestingPurpose();
}
+
+ @Test
+ public void testSetFontVariationSettings_Cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Paint paint = new Paint(PAINT);
+ Typeface.clearTypefaceCachesForTestingPurpose();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ paint.setTypeface(null);
+ paint.setFontVariationSettings(null);
+ state.resumeTiming();
+
+ paint.setFontVariationSettings("'wght' 450");
+ }
+
+ Typeface.clearTypefaceCachesForTestingPurpose();
+ }
+
}
diff --git a/api/Android.bp b/api/Android.bp
index d931df1..341be3d53 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -284,7 +284,7 @@
// These are libs from framework-internal-utils that are required (i.e. being referenced)
// from framework-non-updatable-sources. Add more here when there's a need.
// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
-// dependencies gets bigger.
+// dependencies when the list gets bigger.
android_non_updatable_stubs_libs = [
"android.hardware.cas-V1.2-java",
"android.hardware.health-V1.0-java-constants",
@@ -384,6 +384,11 @@
"sdk_system_current_android",
]
+java_defaults {
+ name: "module-classpath-java-defaults",
+ libs: non_updatable_api_deps_on_modules,
+}
+
// Defaults with module APIs in the classpath (mostly from prebuilts).
// Suitable for compiling android-non-updatable.
stubs_defaults {
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 8dfddf0..d991da5 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -563,8 +563,12 @@
java_defaults {
name: "android-non-updatable_from_text_defaults",
+ defaults: ["android-non-updatable-stubs-libs-defaults"],
static_libs: ["framework-res-package-jar"],
libs: ["stub-annotations"],
+ sdk_version: "none",
+ system_modules: "none",
+ previous_api: ":android.api.public.latest",
}
java_defaults {
@@ -582,10 +586,10 @@
"api-stubs-docs-non-updatable.api.contribution",
],
defaults: ["android-non-updatable_everything_from_text_defaults"],
- full_api_surface_stub: "android_stubs_current.from-text",
// Use full Android API not just the non-updatable API as the latter is incomplete
// and can result in incorrect behavior.
previous_api: ":android.api.combined.public.latest",
+ libs: ["all-modules-public-stubs"],
}
java_api_library {
@@ -596,10 +600,10 @@
"system-api-stubs-docs-non-updatable.api.contribution",
],
defaults: ["android-non-updatable_everything_from_text_defaults"],
- full_api_surface_stub: "android_system_stubs_current.from-text",
// Use full Android API not just the non-updatable API as the latter is incomplete
// and can result in incorrect behavior.
previous_api: ":android.api.combined.system.latest",
+ libs: ["all-modules-system-stubs"],
}
java_api_library {
@@ -611,10 +615,10 @@
"test-api-stubs-docs-non-updatable.api.contribution",
],
defaults: ["android-non-updatable_everything_from_text_defaults"],
- full_api_surface_stub: "android_test_stubs_current.from-text",
// Use full Android API not just the non-updatable API as the latter is incomplete
// and can result in incorrect behavior.
previous_api: ":android.api.combined.test.latest",
+ libs: ["all-modules-system-stubs"],
}
java_api_library {
@@ -625,8 +629,10 @@
"system-api-stubs-docs-non-updatable.api.contribution",
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_everything_from_text_defaults"],
- full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
+ defaults: [
+ "module-classpath-java-defaults",
+ "android-non-updatable_everything_from_text_defaults",
+ ],
// Use full Android API not just the non-updatable API as the latter is incomplete
// and can result in incorrect behavior.
previous_api: ":android.api.combined.module-lib.latest",
@@ -644,14 +650,16 @@
"test-api-stubs-docs-non-updatable.api.contribution",
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_everything_from_text_defaults"],
- full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
+ defaults: [
+ "module-classpath-java-defaults",
+ "android-non-updatable_everything_from_text_defaults",
+ ],
// No need to specify previous_api as this is not used for compiling against.
-
// This module is only used for hiddenapi, and other modules should not
// depend on this module.
visibility: ["//visibility:private"],
+ libs: ["all-modules-system-stubs"],
}
java_defaults {
@@ -665,7 +673,7 @@
}
java_library {
- name: "android_stubs_current.from-source",
+ name: "android_stubs_current",
static_libs: [
"all-modules-public-stubs",
"android-non-updatable.stubs",
@@ -675,7 +683,7 @@
}
java_library {
- name: "android_stubs_current_exportable.from-source",
+ name: "android_stubs_current_exportable",
static_libs: [
"all-modules-public-stubs-exportable",
"android-non-updatable.stubs.exportable",
@@ -685,7 +693,7 @@
}
java_library {
- name: "android_system_stubs_current.from-source",
+ name: "android_system_stubs_current",
static_libs: [
"all-modules-system-stubs",
"android-non-updatable.stubs.system",
@@ -698,7 +706,7 @@
}
java_library {
- name: "android_system_stubs_current_exportable.from-source",
+ name: "android_system_stubs_current_exportable",
static_libs: [
"all-modules-system-stubs-exportable",
"android-non-updatable.stubs.exportable.system",
@@ -722,7 +730,7 @@
}
java_library {
- name: "android_test_stubs_current.from-source",
+ name: "android_test_stubs_current",
static_libs: [
// Updatable modules do not have test APIs, but we want to include their SystemApis, like we
// include the SystemApi of framework-non-updatable-sources.
@@ -739,7 +747,7 @@
}
java_library {
- name: "android_test_stubs_current_exportable.from-source",
+ name: "android_test_stubs_current_exportable",
static_libs: [
// Updatable modules do not have test APIs, but we want to include their SystemApis, like we
// include the SystemApi of framework-non-updatable-sources.
@@ -760,7 +768,7 @@
// This module does not need to be copied to dist
java_library {
- name: "android_test_frameworks_core_stubs_current.from-source",
+ name: "android_test_frameworks_core_stubs_current",
static_libs: [
"all-updatable-modules-system-stubs",
"android-non-updatable.stubs.test",
@@ -772,7 +780,7 @@
}
java_library {
- name: "android_module_lib_stubs_current.from-source",
+ name: "android_module_lib_stubs_current",
defaults: [
"android.jar_defaults",
],
@@ -785,7 +793,7 @@
}
java_library {
- name: "android_module_lib_stubs_current_exportable.from-source",
+ name: "android_module_lib_stubs_current_exportable",
defaults: [
"android.jar_defaults",
"android_stubs_dists_default",
@@ -801,20 +809,20 @@
}
java_library {
- name: "android_system_server_stubs_current.from-source",
+ name: "android_system_server_stubs_current",
defaults: [
"android.jar_defaults",
],
srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
- "android_module_lib_stubs_current.from-source",
+ "android_module_lib_stubs_current",
],
visibility: ["//frameworks/base/services"],
}
java_library {
- name: "android_system_server_stubs_current_exportable.from-source",
+ name: "android_system_server_stubs_current_exportable",
defaults: [
"android.jar_defaults",
"android_stubs_dists_default",
@@ -822,7 +830,7 @@
srcs: [":services-non-updatable-stubs{.exportable}"],
installable: false,
static_libs: [
- "android_module_lib_stubs_current_exportable.from-source",
+ "android_module_lib_stubs_current_exportable",
],
dist: {
dir: "apistubs/android/system-server",
@@ -897,215 +905,6 @@
},
}
-//
-// Java API defaults and libraries for single tree build
-//
-
-java_defaults {
- name: "stub-annotation-defaults",
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- // stub annotations do not contribute to the API surfaces but are statically
- // linked in the stubs for API surfaces (see frameworks/base/StubLibraries.bp).
- // This is because annotation processors insist on loading the classes for any
- // annotations found, thus should exist inside android.jar.
- "private-stub-annotations-jar",
- ],
- is_stubs_module: true,
-}
-
-// Listing of API domains contribution and dependencies per API surfaces
-java_defaults {
- name: "android_test_stubs_current_contributions",
- api_surface: "test",
- api_contributions: [
- "framework-virtualization.stubs.source.test.api.contribution",
- "framework-location.stubs.source.test.api.contribution",
- ],
-}
-
-java_defaults {
- name: "android_test_frameworks_core_stubs_current_contributions",
- api_surface: "test",
- api_contributions: [
- "test-api-stubs-docs-non-updatable.api.contribution",
- ],
-}
-
-java_defaults {
- name: "android_module_lib_stubs_current_contributions",
- api_surface: "module-lib",
- api_contributions: [
- "api-stubs-docs-non-updatable.api.contribution",
- "system-api-stubs-docs-non-updatable.api.contribution",
- "module-lib-api-stubs-docs-non-updatable.api.contribution",
- "art.module.public.api.stubs.source.api.contribution",
- "art.module.public.api.stubs.source.system.api.contribution",
- "art.module.public.api.stubs.source.module_lib.api.contribution",
- "i18n.module.public.api.stubs.source.api.contribution",
- "i18n.module.public.api.stubs.source.system.api.contribution",
- "i18n.module.public.api.stubs.source.module_lib.api.contribution",
- ],
- previous_api: ":android.api.combined.module-lib.latest",
-}
-
-// Java API library definitions per API surface
-java_api_library {
- name: "android_stubs_current.from-text",
- api_surface: "public",
- defaults: [
- // This module is dynamically created at frameworks/base/api/api.go
- // instead of being written out, in order to minimize edits in the codebase
- // when there is a change in the list of modules.
- // that contributes to an api surface.
- "android_stubs_current_contributions",
- "stub-annotation-defaults",
- ],
- api_contributions: [
- "api-stubs-docs-non-updatable.api.contribution",
- ],
- visibility: ["//visibility:public"],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_system_stubs_current.from-text",
- api_surface: "system",
- defaults: [
- "android_stubs_current_contributions",
- "android_system_stubs_current_contributions",
- "stub-annotation-defaults",
- ],
- api_contributions: [
- "api-stubs-docs-non-updatable.api.contribution",
- "system-api-stubs-docs-non-updatable.api.contribution",
- ],
- visibility: ["//visibility:public"],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_test_stubs_current.from-text",
- api_surface: "test",
- defaults: [
- "android_stubs_current_contributions",
- "android_system_stubs_current_contributions",
- "android_test_stubs_current_contributions",
- "stub-annotation-defaults",
- ],
- api_contributions: [
- "api-stubs-docs-non-updatable.api.contribution",
- "system-api-stubs-docs-non-updatable.api.contribution",
- "test-api-stubs-docs-non-updatable.api.contribution",
- ],
- visibility: ["//visibility:public"],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_test_frameworks_core_stubs_current.from-text",
- api_surface: "test",
- defaults: [
- "android_stubs_current_contributions",
- "android_system_stubs_current_contributions",
- "android_test_frameworks_core_stubs_current_contributions",
- ],
- libs: [
- "stub-annotations",
- ],
- api_contributions: [
- "api-stubs-docs-non-updatable.api.contribution",
- "system-api-stubs-docs-non-updatable.api.contribution",
- ],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_module_lib_stubs_current_full.from-text",
- api_surface: "module-lib",
- defaults: [
- "android_stubs_current_contributions",
- "android_system_stubs_current_contributions",
- "android_module_lib_stubs_current_contributions_full",
- ],
- libs: [
- "stub-annotations",
- ],
- api_contributions: [
- "api-stubs-docs-non-updatable.api.contribution",
- "system-api-stubs-docs-non-updatable.api.contribution",
- "module-lib-api-stubs-docs-non-updatable.api.contribution",
- ],
- visibility: ["//visibility:public"],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_module_lib_stubs_current.from-text",
- api_surface: "module-lib",
- defaults: [
- "android_module_lib_stubs_current_contributions",
- ],
- libs: [
- "android_module_lib_stubs_current_full.from-text",
- "stub-annotations",
- ],
- visibility: ["//visibility:public"],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_test_module_lib_stubs_current.from-text",
- api_surface: "module-lib",
- defaults: [
- "android_stubs_current_contributions",
- "android_system_stubs_current_contributions",
- "android_test_stubs_current_contributions",
- "android_module_lib_stubs_current_contributions",
- ],
- libs: [
- "android_module_lib_stubs_current_full.from-text",
- "stub-annotations",
- ],
- api_contributions: [
- "test-api-stubs-docs-non-updatable.api.contribution",
- ],
-
- // This module is only used to build android-non-updatable.stubs.test_module_lib
- // and other modules should not depend on this module.
- visibility: [
- "//visibility:private",
- ],
- enable_validation: false,
- stubs_type: "everything",
-}
-
-java_api_library {
- name: "android_system_server_stubs_current.from-text",
- api_surface: "system-server",
- api_contributions: [
- "services-non-updatable-stubs.api.contribution",
- ],
- libs: [
- "android_module_lib_stubs_current.from-text",
- "stub-annotations",
- ],
- static_libs: [
- "android_module_lib_stubs_current.from-text",
- ],
- visibility: ["//visibility:public"],
- enable_validation: false,
- stubs_type: "everything",
-}
-
////////////////////////////////////////////////////////////////////////
// api-versions.xml generation, for public and system. This API database
// also contains the android.test.* APIs.
diff --git a/api/api.go b/api/api.go
index b6b1a7e..5b7f534 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,9 +15,7 @@
package api
import (
- "fmt"
"sort"
- "strings"
"github.com/google/blueprint/proptools"
@@ -464,79 +462,6 @@
}
}
-func createApiContributionDefaults(ctx android.LoadHookContext, modules []string) {
- defaultsSdkKinds := []android.SdkKind{
- android.SdkPublic, android.SdkSystem, android.SdkModule,
- }
- for _, sdkKind := range defaultsSdkKinds {
- props := defaultsProps{}
- props.Name = proptools.StringPtr(
- sdkKind.DefaultJavaLibraryName() + "_contributions")
- if sdkKind == android.SdkModule {
- props.Name = proptools.StringPtr(
- sdkKind.DefaultJavaLibraryName() + "_contributions_full")
- }
- props.Api_surface = proptools.StringPtr(sdkKind.String())
- apiSuffix := ""
- if sdkKind != android.SdkPublic {
- apiSuffix = "." + strings.ReplaceAll(sdkKind.String(), "-", "_")
- }
- props.Api_contributions = transformArray(
- modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix))
- props.Defaults_visibility = []string{"//visibility:public"}
- props.Previous_api = proptools.StringPtr(":android.api.combined." + sdkKind.String() + ".latest")
- ctx.CreateModule(java.DefaultsFactory, &props)
- }
-}
-
-func createFullApiLibraries(ctx android.LoadHookContext) {
- javaLibraryNames := []string{
- "android_stubs_current",
- "android_system_stubs_current",
- "android_test_stubs_current",
- "android_test_frameworks_core_stubs_current",
- "android_module_lib_stubs_current",
- "android_system_server_stubs_current",
- }
-
- for _, libraryName := range javaLibraryNames {
- props := libraryProps{}
- props.Name = proptools.StringPtr(libraryName)
- staticLib := libraryName + ".from-source"
- if ctx.Config().BuildFromTextStub() {
- staticLib = libraryName + ".from-text"
- }
- props.Static_libs = []string{staticLib}
- props.Defaults = []string{"android.jar_defaults"}
- props.Visibility = []string{"//visibility:public"}
- props.Is_stubs_module = proptools.BoolPtr(true)
-
- ctx.CreateModule(java.LibraryFactory, &props)
- }
-}
-
-func createFullExportableApiLibraries(ctx android.LoadHookContext) {
- javaLibraryNames := []string{
- "android_stubs_current_exportable",
- "android_system_stubs_current_exportable",
- "android_test_stubs_current_exportable",
- "android_module_lib_stubs_current_exportable",
- "android_system_server_stubs_current_exportable",
- }
-
- for _, libraryName := range javaLibraryNames {
- props := libraryProps{}
- props.Name = proptools.StringPtr(libraryName)
- staticLib := libraryName + ".from-source"
- props.Static_libs = []string{staticLib}
- props.Defaults = []string{"android.jar_defaults"}
- props.Visibility = []string{"//visibility:public"}
- props.Is_stubs_module = proptools.BoolPtr(true)
-
- ctx.CreateModule(java.LibraryFactory, &props)
- }
-}
-
func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
bootclasspath := a.bootclasspath(ctx)
system_server_classpath := a.systemServerClasspath(ctx)
@@ -562,12 +487,6 @@
createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
createPublicStubsSourceFilegroup(ctx, bootclasspath)
-
- createApiContributionDefaults(ctx, bootclasspath)
-
- createFullApiLibraries(ctx)
-
- createFullExportableApiLibraries(ctx)
}
func combinedApisModuleFactory() android.Module {
diff --git a/api/api_test.go b/api/api_test.go
index 47d1670..fb26f82 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -52,6 +52,12 @@
"core.current.stubs",
"ext",
"framework",
+ "android_stubs_current",
+ "android_system_stubs_current",
+ "android_test_stubs_current",
+ "android_test_frameworks_core_stubs_current",
+ "android_module_lib_stubs_current",
+ "android_system_server_stubs_current",
"android_stubs_current.from-text",
"android_system_stubs_current.from-text",
"android_test_stubs_current.from-text",
@@ -190,61 +196,60 @@
}
}),
).RunTestWithBp(t, `
- java_sdk_library {
- name: "framework-foo",
- srcs: ["a.java"],
- public: {
- enabled: true,
- },
- system: {
- enabled: true,
- },
- test: {
- enabled: true,
- },
- module_lib: {
- enabled: true,
- },
- api_packages: [
- "foo",
- ],
- sdk_version: "core_current",
- annotations_enabled: true,
- }
+ java_sdk_library {
+ name: "framework-foo",
+ srcs: ["a.java"],
+ public: {
+ enabled: true,
+ },
+ system: {
+ enabled: true,
+ },
+ test: {
+ enabled: true,
+ },
+ module_lib: {
+ enabled: true,
+ },
+ api_packages: [
+ "foo",
+ ],
+ sdk_version: "core_current",
+ annotations_enabled: true,
+ }
+ java_sdk_library {
+ name: "framework-bar",
+ srcs: ["a.java"],
+ public: {
+ enabled: true,
+ },
+ system: {
+ enabled: true,
+ },
+ test: {
+ enabled: true,
+ },
+ module_lib: {
+ enabled: true,
+ },
+ api_packages: [
+ "foo",
+ ],
+ sdk_version: "core_current",
+ annotations_enabled: true,
+ }
- java_sdk_library {
- name: "framework-bar",
- srcs: ["a.java"],
- public: {
- enabled: true,
- },
- system: {
- enabled: true,
- },
- test: {
- enabled: true,
- },
- module_lib: {
- enabled: true,
- },
- api_packages: [
- "foo",
+ combined_apis {
+ name: "foo",
+ bootclasspath: [
+ "framework-bar",
+ ] + select(boolean_var_for_testing(), {
+ true: [
+ "framework-foo",
],
- sdk_version: "core_current",
- annotations_enabled: true,
- }
-
- combined_apis {
- name: "foo",
- bootclasspath: [
- "framework-bar",
- ] + select(boolean_var_for_testing(), {
- true: [
- "framework-foo",
- ],
- default: [],
- }),
- }
+ default: [],
+ }),
+ }
`)
subModuleDependsOnSelectAppendedModule := java.CheckModuleHasDependency(t,
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index f264125..6902d6d 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -78,6 +78,11 @@
namespace android::os {
+template <typename T>
+const T* Idmap2Service::GetPointer(const OwningPtr<T>& ptr) {
+ return std::visit([](auto&& ptr) { return ptr.get(); }, ptr);
+}
+
Status Idmap2Service::getIdmapPath(const std::string& overlay_path,
int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
assert(_aidl_return);
@@ -224,7 +229,7 @@
if (is_framework ||
(item.dev == st.st_dev && item.inode == st.st_ino && item.size == st.st_size
&& item.mtime.tv_sec == st.st_mtim.tv_sec && item.mtime.tv_nsec == st.st_mtim.tv_nsec)) {
- return {item.apk.get()};
+ return {item.apk};
}
container_cache_.erase(cache_it);
}
@@ -238,14 +243,14 @@
return {std::move(*target)};
}
- const auto res = target->get();
+ auto res = std::shared_ptr(std::move(*target));
std::lock_guard lock(container_cache_mutex_);
container_cache_.emplace(target_path, CachedContainer {
.dev = dev_t(st.st_dev),
.inode = ino_t(st.st_ino),
.size = st.st_size,
.mtime = st.st_mtim,
- .apk = std::move(*target)
+ .apk = res
});
return {res};
}
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index a69fa61..272ec6b 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -85,7 +85,7 @@
ino_t inode;
int64_t size;
struct timespec mtime;
- std::unique_ptr<idmap2::TargetResourceContainer> apk;
+ std::shared_ptr<idmap2::TargetResourceContainer> apk;
};
std::unordered_map<std::string, CachedContainer> container_cache_;
std::mutex container_cache_mutex_;
@@ -95,24 +95,15 @@
std::mutex frro_iter_mutex_;
template <typename T>
- using MaybeUniquePtr = std::variant<std::unique_ptr<T>, T*>;
+ using OwningPtr = std::variant<std::unique_ptr<T>, std::shared_ptr<T>>;
- using TargetResourceContainerPtr = MaybeUniquePtr<idmap2::TargetResourceContainer>;
+ using TargetResourceContainerPtr = OwningPtr<idmap2::TargetResourceContainer>;
idmap2::Result<TargetResourceContainerPtr> GetTargetContainer(const std::string& target_path);
template <typename T>
- WARN_UNUSED static const T* GetPointer(const MaybeUniquePtr<T>& ptr);
+ WARN_UNUSED static const T* GetPointer(const OwningPtr<T>& ptr);
};
-template <typename T>
-const T* Idmap2Service::GetPointer(const MaybeUniquePtr<T>& ptr) {
- auto u = std::get_if<T*>(&ptr);
- if (u != nullptr) {
- return *u;
- }
- return std::get<std::unique_ptr<T>>(ptr).get();
-}
-
} // namespace android::os
#endif // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index dd2569a..a413bbd 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -1,23 +1,13 @@
android.content.AsyncTaskLoader$LoadTask
-android.media.MediaCodecInfo$CodecCapabilities$FeatureList
-android.media.MediaCodecInfo$LazyHolder
android.net.ConnectivityThread$Singleton
+android.os.FileObserver
+android.os.NullVibrator
+android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
+android.widget.Magnifier
+gov.nist.core.net.DefaultNetworkLayer
android.net.rtp.AudioGroup
android.net.rtp.AudioStream
android.net.rtp.RtpStream
-android.os.FileObserver
-android.os.NullVibrator
-android.permission.PermissionManager
-android.provider.MediaStore
-android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
-android.view.HdrRenderState
-android.text.TextFlags
-android.widget.Magnifier
-com.android.internal.jank.InteractionJankMonitor$InstanceHolder
-com.android.internal.os.BinderCallsStats$SettingsObserver
-com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
-com.android.server.BootReceiver$2
-gov.nist.core.net.DefaultNetworkLayer
-java.util.ImmutableCollections
java.util.concurrent.ThreadLocalRandom
-sun.nio.fs.UnixChannelFactory
+java.util.ImmutableCollections
+com.android.internal.jank.InteractionJankMonitor$InstanceHolder
diff --git a/core/api/current.txt b/core/api/current.txt
index 354e26b..ea039a7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -59034,7 +59034,7 @@
}
public static interface CompoundButton.OnCheckedChangeListener {
- method public void onCheckedChanged(android.widget.CompoundButton, boolean);
+ method public void onCheckedChanged(@NonNull android.widget.CompoundButton, boolean);
}
public abstract class CursorAdapter extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter {
@@ -60099,7 +60099,7 @@
}
public static interface RadioGroup.OnCheckedChangeListener {
- method public void onCheckedChanged(android.widget.RadioGroup, @IdRes int);
+ method public void onCheckedChanged(@NonNull android.widget.RadioGroup, @IdRes int);
}
public class RatingBar extends android.widget.AbsSeekBar {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 15e5706..445a572 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11981,6 +11981,7 @@
field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
field public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS";
field public static final String ACTION_MANAGE_MORE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS";
+ field @FlaggedApi("android.nfc.nfc_action_manage_services_settings") public static final String ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS = "android.settings.MANAGE_OTHER_NFC_SERVICES_SETTINGS";
field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS";
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 5214d2c..21396a1 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -10768,8 +10768,13 @@
final long key = makeKey(uidState, flag);
NoteOpEvent event = events.get(key);
- if (lastEvent == null
- || event != null && event.getNoteTime() > lastEvent.getNoteTime()) {
+ if (event == null) {
+ continue;
+ }
+
+ if (lastEvent == null || event.getNoteTime() > lastEvent.getNoteTime()
+ || (event.getNoteTime() == lastEvent.getNoteTime()
+ && event.getDuration() > lastEvent.getDuration())) {
lastEvent = event;
}
}
@@ -11013,7 +11018,8 @@
if (access != null) {
NoteOpEvent existingAccess = accessEvents.get(key);
- if (existingAccess == null || existingAccess.getDuration() == -1) {
+ if (existingAccess == null || existingAccess.getDuration() == -1
+ || existingAccess.getDuration() < access.getDuration()) {
accessEvents.append(key, access);
} else if (existingAccess.mProxy == null && access.mProxy != null ) {
existingAccess.mProxy = access.mProxy;
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index b3fc058..0819833 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -105,17 +105,10 @@
public @NonNull ComponentOptions setPendingIntentBackgroundActivityStartMode(
@BackgroundActivityStartMode int state) {
switch (state) {
- case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
- if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) {
- // do not overwrite ALWAYS with ALLOWED for backwards compatibility,
- // if setPendingIntentBackgroundActivityLaunchAllowedByPermission is used
- // before this method.
- mPendingIntentBalAllowed = state;
- }
- break;
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
case MODE_BACKGROUND_ACTIVITY_START_DENIED:
case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS:
case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE:
mPendingIntentBalAllowed = state;
@@ -139,35 +132,6 @@
return mPendingIntentBalAllowed;
}
- /**
- * Get PendingIntent activity is allowed to be started in the background if the caller
- * has BAL permission.
- * @hide
- * @deprecated check for #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- */
- @Deprecated
- public boolean isPendingIntentBackgroundActivityLaunchAllowedByPermission() {
- return mPendingIntentBalAllowed == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
- }
-
- /**
- * Set PendingIntent activity can be launched from background if caller has BAL permission.
- * @hide
- * @deprecated use #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- */
- @Deprecated
- public void setPendingIntentBackgroundActivityLaunchAllowedByPermission(boolean allowed) {
- if (allowed) {
- setPendingIntentBackgroundActivityStartMode(
- MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
- } else {
- if (getPendingIntentBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS) {
- setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- }
- }
- }
-
/** @hide */
public Bundle toBundle() {
Bundle b = new Bundle();
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 4a5836c..b06fb9e 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -15,11 +15,16 @@
*/
package android.app;
+import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -27,9 +32,12 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import android.util.SparseArray;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
/**
* A no-op implementation of {@link WallpaperManager}.
@@ -54,29 +62,19 @@
private DisabledWallpaperManager() {
}
- @Override
- public boolean isWallpaperSupported() {
- return false;
+ @UnsupportedAppUsage
+ public IWallpaperManager getIWallpaperManager() {
+ return unsupported();
}
@Override
- public boolean isSetWallpaperAllowed() {
- return false;
+ public boolean isLockscreenLiveWallpaperEnabled() {
+ return unsupportedBoolean();
}
- private static <T> T unsupported() {
- if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
- return null;
- }
-
- private static boolean unsupportedBoolean() {
- if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
- return false;
- }
-
- private static int unsupportedInt() {
- if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
- return -1;
+ @Override
+ public boolean shouldEnableWideColorGamut() {
+ return unsupportedBoolean();
}
@Override
@@ -122,6 +120,11 @@
}
@Override
+ public boolean wallpaperSupportsWcg(int which) {
+ return unsupportedBoolean();
+ }
+
+ @Override
public Bitmap getBitmap() {
return unsupported();
}
@@ -131,12 +134,61 @@
return unsupported();
}
+ @Nullable
+ public Bitmap getBitmap(boolean hardware, @SetWallpaperFlags int which) {
+ return unsupported();
+ }
+
@Override
public Bitmap getBitmapAsUser(int userId, boolean hardware) {
return unsupported();
}
@Override
+ public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+ return unsupported();
+ }
+
+ @Override
+ public Bitmap getBitmapAsUser(int userId, boolean hardware,
+ @SetWallpaperFlags int which, boolean returnDefault) {
+ return unsupported();
+ }
+
+ @Override
+ public Rect peekBitmapDimensions() {
+ return unsupported();
+ }
+
+ @Override
+ public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+ return unsupported();
+ }
+
+ @Nullable
+ public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
+ return unsupported();
+ }
+
+ @Override
+ public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
+ @SetWallpaperFlags int which, boolean originalBitmap) {
+ return unsupported();
+ }
+
+ @Override
+ public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
+ @Nullable Map<Point, Rect> cropHints) {
+ return unsupported();
+ }
+
+ @Override
+ public WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap,
+ @Nullable Map<Point, Rect> cropHints) {
+ return unsupported();
+ }
+
+ @Override
public ParcelFileDescriptor getWallpaperFile(int which) {
return unsupported();
}
@@ -173,6 +225,17 @@
}
@Override
+ public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
+ List<RectF> regions, int which) throws IllegalArgumentException {
+ unsupported();
+ }
+
+ @Override
+ public void removeOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback) {
+ unsupported();
+ }
+
+ @Override
public ParcelFileDescriptor getWallpaperFile(int which, int userId) {
return unsupported();
}
@@ -192,12 +255,6 @@
return unsupported();
}
- @Override
- public ParcelFileDescriptor getWallpaperInfoFile() {
- return unsupported();
- }
-
- @Override
public WallpaperInfo getWallpaperInfoForUser(int userId) {
return unsupported();
}
@@ -213,6 +270,11 @@
}
@Override
+ public ParcelFileDescriptor getWallpaperInfoFile() {
+ return unsupported();
+ }
+
+ @Override
public int getWallpaperId(int which) {
return unsupportedInt();
}
@@ -264,6 +326,11 @@
return 0;
}
+ public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ return unsupportedInt();
+ }
+
@Override
public void setStream(InputStream bitmapData) throws IOException {
unsupported();
@@ -284,6 +351,19 @@
}
@Override
+ public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ return unsupportedInt();
+ }
+
+
+ @Override
+ public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
+ boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+ return unsupportedInt();
+ }
+
+ @Override
public boolean hasResourceWallpaper(int resid) {
return unsupportedBoolean();
}
@@ -328,12 +408,40 @@
return unsupportedBoolean();
}
+
+ @Override
+ public void setWallpaperDimAmount(@FloatRange(from = 0f, to = 1f) float dimAmount) {
+ unsupported();
+ }
+
+ @Override
+ public @FloatRange(from = 0f, to = 1f) float getWallpaperDimAmount() {
+ return unsupportedInt();
+ }
+
+ @Override
+ public boolean lockScreenWallpaperExists() {
+ return unsupportedBoolean();
+ }
+
@Override
public boolean setWallpaperComponent(ComponentName name, int userId) {
return unsupportedBoolean();
}
@Override
+ public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+ @SetWallpaperFlags int which) {
+ return unsupportedBoolean();
+ }
+
+ @Override
+ public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
+ @SetWallpaperFlags int which, int userId) {
+ return unsupportedBoolean();
+ }
+
+ @Override
public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
unsupported();
}
@@ -350,6 +458,21 @@
}
@Override
+ public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
+ unsupported();
+ }
+
+ @Override
+ public boolean isWallpaperSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isSetWallpaperAllowed() {
+ return false;
+ }
+
+ @Override
public void clearWallpaperOffsets(IBinder windowToken) {
unsupported();
}
@@ -369,8 +492,18 @@
return unsupportedBoolean();
}
- @Override
- public boolean wallpaperSupportsWcg(int which) {
- return unsupportedBoolean();
+ private static <T> T unsupported() {
+ if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
+ return null;
+ }
+
+ private static boolean unsupportedBoolean() {
+ if (DEBUG) Log.w(TAG, "unsupported method called; returning false", new Exception());
+ return false;
+ }
+
+ private static int unsupportedInt() {
+ if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception());
+ return -1;
}
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index fd4d8e9..0cc210b 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -1836,9 +1836,10 @@
// have shared library asset paths appended if there are any.
if (r.getImpl() != null) {
final ResourcesImpl oldImpl = r.getImpl();
+ final AssetManager oldAssets = oldImpl.getAssets();
// ResourcesImpl constructor will help to append shared library asset paths.
- if (oldImpl.getAssets().isUpToDate()) {
- final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(),
+ if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) {
+ final ResourcesImpl newImpl = new ResourcesImpl(oldAssets,
oldImpl.getMetrics(), oldImpl.getConfiguration(),
oldImpl.getDisplayAdjustments());
r.setImpl(newImpl);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 1a72df1..5903a7f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -123,6 +123,8 @@
* <p> An app can check whether wallpapers are supported for the current user, by calling
* {@link #isWallpaperSupported()}, and whether setting of wallpapers is allowed, by calling
* {@link #isSetWallpaperAllowed()}.
+ * Any public APIs added to WallpaperManager should have a corresponding stub in
+ * {@link DisabledWallpaperManager}.
*/
@SystemService(Context.WALLPAPER_SERVICE)
public class WallpaperManager {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index c789af3..9148e3c 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -37,7 +37,6 @@
}
}
-
flag {
name: "onboarding_bugreport_v2_enabled"
is_exported: true
@@ -403,3 +402,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "dont_read_policy_definition"
+ namespace: "enterprise"
+ description: "Rely on <policy-key-entry> to determine policy definition and ignore <policy-definition-entry>"
+ bug: "335663055"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8365840..9aebfc8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6676,6 +6676,16 @@
public static final String BLOCKED_NUMBERS_SERVICE = "blocked_numbers";
/**
+ * Use with {@link #getSystemService(String)} to retrieve the
+ * {@link com.android.internal.protolog.ProtoLogService} for registering ProtoLog clients.
+ *
+ * @see #getSystemService(String)
+ * @see com.android.internal.protolog.ProtoLogService
+ * @hide
+ */
+ public static final String PROTOLOG_SERVICE = "protolog";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 111e6a8..cb57c7b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7485,7 +7485,7 @@
/**
* This flag is only used for split-screen multi-window mode. The new activity will be displayed
- * adjacent to the one launching it. This can only be used in conjunction with
+ * adjacent to the one launching it if possible. This can only be used in conjunction with
* {@link #FLAG_ACTIVITY_NEW_TASK}. Also, setting {@link #FLAG_ACTIVITY_MULTIPLE_TASK} is
* required if you want a new instance of an existing activity to be created.
*/
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 835459e..59fca3b 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -151,6 +151,16 @@
}
flag {
+ name: "fix_avatar_cross_user_leak"
+ namespace: "multiuser"
+ description: "Fix cross-user picture uri leak for avatar picker apps."
+ bug: "341688848"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fix_get_user_property_cache"
namespace: "multiuser"
description: "Cache is not optimised for getUserProperty for values below 0, eg. UserHandler.USER_NULL or UserHandle.USER_ALL"
@@ -381,8 +391,12 @@
}
flag {
- name: "unicorn_mode_refactoring_for_hsum"
+ name: "unicorn_mode_refactoring_for_hsum_read_only"
namespace: "multiuser"
- description: "Refactorings related to unicorn mode to work on HSUM mode"
+ description: "Refactorings related to unicorn mode to work on HSUM mode (Read only flag)"
bug: "339201286"
-}
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 678bd6b..de1cac4 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -415,7 +415,7 @@
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
- return new BiometricTestSession(mContext, sensorId,
+ return new BiometricTestSession(mContext, getSensorProperties(), sensorId,
(context, sensorId1, callback) -> mService
.createTestSession(sensorId1, callback, context.getOpPackageName()));
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
index 027d101..8bd3528 100644
--- a/core/java/android/hardware/biometrics/BiometricTestSession.java
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -27,12 +27,15 @@
import android.util.ArraySet;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
* {@link android.hardware.fingerprint.FingerprintManager}.
+ *
* @hide
*/
@TestApi
@@ -48,21 +51,29 @@
@NonNull ITestSessionCallback callback) throws RemoteException;
}
- private final Context mContext;
private final int mSensorId;
- private final ITestSession mTestSession;
+ private final List<ITestSession> mTestSessionsForAllSensors = new ArrayList<>();
+ private ITestSession mTestSession;
// Keep track of users that were tested, which need to be cleaned up when finishing.
- @NonNull private final ArraySet<Integer> mTestedUsers;
+ @NonNull
+ private final ArraySet<Integer> mTestedUsers;
// Track the users currently cleaning up, and provide a latch that gets notified when all
// users have finished cleaning up. This is an imperfect system, as there can technically be
// multiple cleanups per user. Theoretically we should track the cleanup's BaseClientMonitor's
// unique ID, but it's complicated to plumb it through. This should be fine for now.
- @Nullable private CountDownLatch mCloseLatch;
- @NonNull private final ArraySet<Integer> mUsersCleaningUp;
+ @Nullable
+ private CountDownLatch mCloseLatch;
+ @NonNull
+ private final ArraySet<Integer> mUsersCleaningUp;
- private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() {
+ private class TestSessionCallbackIml extends ITestSessionCallback.Stub {
+ private final int mSensorId;
+ private TestSessionCallbackIml(int sensorId) {
+ mSensorId = sensorId;
+ }
+
@Override
public void onCleanupStarted(int userId) {
Log.d(getTag(), "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId);
@@ -76,19 +87,30 @@
mUsersCleaningUp.remove(userId);
if (mUsersCleaningUp.isEmpty() && mCloseLatch != null) {
+ Log.d(getTag(), "counting down");
mCloseLatch.countDown();
}
}
- };
+ }
/**
* @hide
*/
- public BiometricTestSession(@NonNull Context context, int sensorId,
- @NonNull TestSessionProvider testSessionProvider) throws RemoteException {
- mContext = context;
+ public BiometricTestSession(@NonNull Context context, List<SensorProperties> sensors,
+ int sensorId, @NonNull TestSessionProvider testSessionProvider) throws RemoteException {
mSensorId = sensorId;
- mTestSession = testSessionProvider.createTestSession(context, sensorId, mCallback);
+ // When any of the sensors should create the test session, all the other sensors should
+ // set test hal enabled too.
+ for (SensorProperties sensor : sensors) {
+ final int id = sensor.getSensorId();
+ final ITestSession session = testSessionProvider.createTestSession(context, id,
+ new TestSessionCallbackIml(id));
+ mTestSessionsForAllSensors.add(session);
+ if (id == sensorId) {
+ mTestSession = session;
+ }
+ }
+
mTestedUsers = new ArraySet<>();
mUsersCleaningUp = new ArraySet<>();
setTestHalEnabled(true);
@@ -107,8 +129,11 @@
@RequiresPermission(TEST_BIOMETRIC)
private void setTestHalEnabled(boolean enabled) {
try {
- Log.w(getTag(), "setTestHalEnabled, sensor: " + mSensorId + " enabled: " + enabled);
- mTestSession.setTestHalEnabled(enabled);
+ for (ITestSession session : mTestSessionsForAllSensors) {
+ Log.w(getTag(), "setTestHalEnabled, sensor: " + session.getSensorId() + " enabled: "
+ + enabled);
+ session.setTestHalEnabled(enabled);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -175,10 +200,12 @@
/**
* Simulates an acquired message from the HAL.
*
- * @param userId User that this command applies to.
+ * @param userId User that this command applies to.
* @param acquireInfo See
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and
- * {@link FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)}
+ * {@link
+ * BiometricPrompt.AuthenticationCallback#onAuthenticationAcquired(int)} and
+ * {@link
+ * FingerprintManager.AuthenticationCallback#onAuthenticationAcquired(int)}
*/
@RequiresPermission(TEST_BIOMETRIC)
public void notifyAcquired(int userId, int acquireInfo) {
@@ -192,10 +219,12 @@
/**
* Simulates an error message from the HAL.
*
- * @param userId User that this command applies to.
+ * @param userId User that this command applies to.
* @param errorCode See
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} and
- * {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int, CharSequence)}
+ * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int,
+ * CharSequence)} and
+ * {@link FingerprintManager.AuthenticationCallback#onAuthenticationError(int,
+ * CharSequence)}
*/
@RequiresPermission(TEST_BIOMETRIC)
public void notifyError(int userId, int errorCode) {
@@ -220,8 +249,20 @@
Log.w(getTag(), "Cleanup already in progress for user: " + userId);
}
- mUsersCleaningUp.add(userId);
- mTestSession.cleanupInternalState(userId);
+ for (ITestSession session : mTestSessionsForAllSensors) {
+ mUsersCleaningUp.add(userId);
+ Log.d(getTag(), "cleanupInternalState for sensor: " + session.getSensorId());
+ mCloseLatch = new CountDownLatch(1);
+ session.cleanupInternalState(userId);
+
+ try {
+ Log.d(getTag(), "Awaiting latch...");
+ mCloseLatch.await(3, TimeUnit.SECONDS);
+ Log.d(getTag(), "Finished awaiting");
+ } catch (InterruptedException e) {
+ Log.e(getTag(), "Latch interrupted", e);
+ }
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -234,18 +275,9 @@
// Cleanup can be performed using the test HAL, since it always responds to enumerate with
// zero enrollments.
if (!mTestedUsers.isEmpty()) {
- mCloseLatch = new CountDownLatch(1);
for (int user : mTestedUsers) {
cleanupInternalState(user);
}
-
- try {
- Log.d(getTag(), "Awaiting latch...");
- mCloseLatch.await(3, TimeUnit.SECONDS);
- Log.d(getTag(), "Finished awaiting");
- } catch (InterruptedException e) {
- Log.e(getTag(), "Latch interrupted", e);
- }
}
if (!mUsersCleaningUp.isEmpty()) {
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
index df9f504..bd99606 100644
--- a/core/java/android/hardware/biometrics/ITestSession.aidl
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -59,4 +59,8 @@
// HAL is disabled (e.g. to clean up after a test).
@EnforcePermission("TEST_BIOMETRIC")
void cleanupInternalState(int userId);
+
+ // Get the sensor id of the current test session.
+ @EnforcePermission("TEST_BIOMETRIC")
+ int getSensorId();
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 7f3c49d..1e7f70b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -918,7 +918,7 @@
checkIfCameraClosedOrInError();
for (String physicalId : physicalCameraIdSet) {
- if (physicalId == getId()) {
+ if (Objects.equals(physicalId, getId())) {
throw new IllegalStateException("Physical id matches the logical id!");
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 903e916..7f1cac0 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -172,7 +172,7 @@
@RequiresPermission(TEST_BIOMETRIC)
public BiometricTestSession createTestSession(int sensorId) {
try {
- return new BiometricTestSession(mContext, sensorId,
+ return new BiometricTestSession(mContext, getSensorProperties(), sensorId,
(context, sensorId1, callback) -> mService
.createTestSession(sensorId1, callback, context.getOpPackageName()));
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1767d64..98e11375 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,7 @@
import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.IKeyboardBacklightListener;
import android.hardware.input.IKeyboardBacklightState;
+import android.hardware.input.IKeyboardSystemShortcutListener;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.KeyboardLayoutSelectionResult;
@@ -239,4 +240,14 @@
void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
KeyGlyphMap getKeyGlyphMap(int deviceId);
+
+ @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
+ void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+
+ @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
+ void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
}
diff --git a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
new file mode 100644
index 0000000..8d44917
--- /dev/null
+++ b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+/** @hide */
+oneway interface IKeyboardSystemShortcutListener {
+
+ /**
+ * Called when the keyboard system shortcut is triggered.
+ */
+ void onKeyboardSystemShortcutTriggered(int deviceId, in int[] keycodes, int modifierState,
+ int shortcut);
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d7952eb..6bc522b 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1378,6 +1378,36 @@
}
/**
+ * Registers a keyboard system shortcut listener for {@link KeyboardSystemShortcut} being
+ * triggered.
+ *
+ * @param executor an executor on which the callback will be called
+ * @param listener the {@link KeyboardSystemShortcutListener}
+ * @throws IllegalArgumentException if {@code listener} has already been registered previously.
+ * @throws NullPointerException if {@code listener} or {@code executor} is null.
+ * @hide
+ * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+ public void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
+ @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+ mGlobal.registerKeyboardSystemShortcutListener(executor, listener);
+ }
+
+ /**
+ * Unregisters a previously added keyboard system shortcut listener.
+ *
+ * @param listener the {@link KeyboardSystemShortcutListener}
+ * @hide
+ * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+ public void unregisterKeyboardSystemShortcutListener(
+ @NonNull KeyboardSystemShortcutListener listener) {
+ mGlobal.unregisterKeyboardSystemShortcutListener(listener);
+ }
+
+ /**
* A callback used to be notified about battery state changes for an input device. The
* {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
* listener is successfully registered to provide the initial battery state of the device.
@@ -1478,4 +1508,21 @@
*/
void onStickyModifierStateChanged(@NonNull StickyModifierState state);
}
+
+ /**
+ * A callback used to be notified about keyboard system shortcuts being triggered.
+ *
+ * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+ * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+ * @hide
+ */
+ public interface KeyboardSystemShortcutListener {
+ /**
+ * Called when a keyboard system shortcut is triggered.
+ *
+ * @param systemShortcut the shortcut info about the shortcut that was triggered.
+ */
+ void onKeyboardSystemShortcutTriggered(int deviceId,
+ @NonNull KeyboardSystemShortcut systemShortcut);
+ }
}
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7b47180..f7fa557 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -26,6 +26,7 @@
import android.hardware.input.InputManager.InputDeviceBatteryListener;
import android.hardware.input.InputManager.InputDeviceListener;
import android.hardware.input.InputManager.KeyboardBacklightListener;
+import android.hardware.input.InputManager.KeyboardSystemShortcutListener;
import android.hardware.input.InputManager.OnTabletModeChangedListener;
import android.hardware.input.InputManager.StickyModifierStateListener;
import android.hardware.lights.Light;
@@ -110,6 +111,14 @@
@Nullable
private IStickyModifierStateListener mStickyModifierStateListener;
+ private final Object mKeyboardSystemShortcutListenerLock = new Object();
+ @GuardedBy("mKeyboardSystemShortcutListenerLock")
+ @Nullable
+ private ArrayList<KeyboardSystemShortcutListenerDelegate> mKeyboardSystemShortcutListeners;
+ @GuardedBy("mKeyboardSystemShortcutListenerLock")
+ @Nullable
+ private IKeyboardSystemShortcutListener mKeyboardSystemShortcutListener;
+
// InputDeviceSensorManager gets notified synchronously from the binder thread when input
// devices change, so it must be synchronized with the input device listeners.
@GuardedBy("mInputDeviceListeners")
@@ -1055,6 +1064,98 @@
}
}
+ private static final class KeyboardSystemShortcutListenerDelegate {
+ final KeyboardSystemShortcutListener mListener;
+ final Executor mExecutor;
+
+ KeyboardSystemShortcutListenerDelegate(KeyboardSystemShortcutListener listener,
+ Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ void onKeyboardSystemShortcutTriggered(int deviceId,
+ KeyboardSystemShortcut systemShortcut) {
+ mExecutor.execute(() ->
+ mListener.onKeyboardSystemShortcutTriggered(deviceId, systemShortcut));
+ }
+ }
+
+ private class LocalKeyboardSystemShortcutListener extends IKeyboardSystemShortcutListener.Stub {
+
+ @Override
+ public void onKeyboardSystemShortcutTriggered(int deviceId, int[] keycodes,
+ int modifierState, int shortcut) {
+ synchronized (mKeyboardSystemShortcutListenerLock) {
+ if (mKeyboardSystemShortcutListeners == null) return;
+ final int numListeners = mKeyboardSystemShortcutListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ mKeyboardSystemShortcutListeners.get(i)
+ .onKeyboardSystemShortcutTriggered(deviceId,
+ new KeyboardSystemShortcut(keycodes, modifierState, shortcut));
+ }
+ }
+ }
+ }
+
+ /**
+ * @see InputManager#registerKeyboardSystemShortcutListener(Executor,
+ * KeyboardSystemShortcutListener)
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+ void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
+ @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+ Objects.requireNonNull(executor, "executor should not be null");
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mKeyboardSystemShortcutListenerLock) {
+ if (mKeyboardSystemShortcutListener == null) {
+ mKeyboardSystemShortcutListeners = new ArrayList<>();
+ mKeyboardSystemShortcutListener = new LocalKeyboardSystemShortcutListener();
+
+ try {
+ mIm.registerKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ final int numListeners = mKeyboardSystemShortcutListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mKeyboardSystemShortcutListeners.get(i).mListener == listener) {
+ throw new IllegalArgumentException("Listener has already been registered!");
+ }
+ }
+ KeyboardSystemShortcutListenerDelegate delegate =
+ new KeyboardSystemShortcutListenerDelegate(listener, executor);
+ mKeyboardSystemShortcutListeners.add(delegate);
+ }
+ }
+
+ /**
+ * @see InputManager#unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+ void unregisterKeyboardSystemShortcutListener(
+ @NonNull KeyboardSystemShortcutListener listener) {
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mKeyboardSystemShortcutListenerLock) {
+ if (mKeyboardSystemShortcutListeners == null) {
+ return;
+ }
+ mKeyboardSystemShortcutListeners.removeIf((delegate) -> delegate.mListener == listener);
+ if (mKeyboardSystemShortcutListeners.isEmpty()) {
+ try {
+ mIm.unregisterKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mKeyboardSystemShortcutListeners = null;
+ mKeyboardSystemShortcutListener = null;
+ }
+ }
+ }
+
/**
* TODO(b/330517633): Cleanup the unsupported API
*/
diff --git a/core/java/android/hardware/input/KeyboardSystemShortcut.java b/core/java/android/hardware/input/KeyboardSystemShortcut.java
new file mode 100644
index 0000000..89cf877
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardSystemShortcut.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the keyboard shortcut being triggered by an external keyboard.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class KeyboardSystemShortcut {
+
+ private static final String TAG = "KeyboardSystemShortcut";
+
+ @NonNull
+ private final int[] mKeycodes;
+ private final int mModifierState;
+ @SystemShortcut
+ private final int mSystemShortcut;
+
+
+ public static final int SYSTEM_SHORTCUT_UNSPECIFIED =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+ public static final int SYSTEM_SHORTCUT_HOME =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
+ public static final int SYSTEM_SHORTCUT_RECENT_APPS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
+ public static final int SYSTEM_SHORTCUT_BACK =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
+ public static final int SYSTEM_SHORTCUT_APP_SWITCH =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
+ public static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
+ public static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
+ public static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
+ public static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
+ public static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
+ public static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
+ public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
+ public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
+ public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
+ public static final int SYSTEM_SHORTCUT_VOLUME_UP =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
+ public static final int SYSTEM_SHORTCUT_VOLUME_DOWN =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
+ public static final int SYSTEM_SHORTCUT_VOLUME_MUTE =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
+ public static final int SYSTEM_SHORTCUT_ALL_APPS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
+ public static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
+ public static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
+ public static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
+ public static final int SYSTEM_SHORTCUT_SYSTEM_MUTE =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
+ public static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
+ public static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+ public static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
+ public static final int SYSTEM_SHORTCUT_LOCK_SCREEN =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
+ public static final int SYSTEM_SHORTCUT_OPEN_NOTES =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
+ public static final int SYSTEM_SHORTCUT_TOGGLE_POWER =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
+ public static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
+ public static final int SYSTEM_SHORTCUT_SLEEP =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
+ public static final int SYSTEM_SHORTCUT_WAKEUP =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
+ public static final int SYSTEM_SHORTCUT_MEDIA_KEY =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+ public static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+ public static final int SYSTEM_SHORTCUT_DESKTOP_MODE =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
+ public static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION =
+ FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @IntDef(prefix = "SYSTEM_SHORTCUT_", value = {
+ SYSTEM_SHORTCUT_UNSPECIFIED,
+ SYSTEM_SHORTCUT_HOME,
+ SYSTEM_SHORTCUT_RECENT_APPS,
+ SYSTEM_SHORTCUT_BACK,
+ SYSTEM_SHORTCUT_APP_SWITCH,
+ SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+ SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+ SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+ SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ SYSTEM_SHORTCUT_TOGGLE_TASKBAR,
+ SYSTEM_SHORTCUT_TAKE_SCREENSHOT,
+ SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+ SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+ SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+ SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+ SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+ SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+ SYSTEM_SHORTCUT_VOLUME_UP,
+ SYSTEM_SHORTCUT_VOLUME_DOWN,
+ SYSTEM_SHORTCUT_VOLUME_MUTE,
+ SYSTEM_SHORTCUT_ALL_APPS,
+ SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+ SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+ SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+ SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+ SYSTEM_SHORTCUT_SYSTEM_MUTE,
+ SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+ SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS,
+ SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT,
+ SYSTEM_SHORTCUT_LOCK_SCREEN,
+ SYSTEM_SHORTCUT_OPEN_NOTES,
+ SYSTEM_SHORTCUT_TOGGLE_POWER,
+ SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+ SYSTEM_SHORTCUT_SLEEP,
+ SYSTEM_SHORTCUT_WAKEUP,
+ SYSTEM_SHORTCUT_MEDIA_KEY,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER,
+ SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS,
+ SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+ SYSTEM_SHORTCUT_DESKTOP_MODE,
+ SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface SystemShortcut {}
+
+ @DataClass.Generated.Member
+ public static String systemShortcutToString(@SystemShortcut int value) {
+ switch (value) {
+ case SYSTEM_SHORTCUT_UNSPECIFIED:
+ return "SYSTEM_SHORTCUT_UNSPECIFIED";
+ case SYSTEM_SHORTCUT_HOME:
+ return "SYSTEM_SHORTCUT_HOME";
+ case SYSTEM_SHORTCUT_RECENT_APPS:
+ return "SYSTEM_SHORTCUT_RECENT_APPS";
+ case SYSTEM_SHORTCUT_BACK:
+ return "SYSTEM_SHORTCUT_BACK";
+ case SYSTEM_SHORTCUT_APP_SWITCH:
+ return "SYSTEM_SHORTCUT_APP_SWITCH";
+ case SYSTEM_SHORTCUT_LAUNCH_ASSISTANT:
+ return "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT";
+ case SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT:
+ return "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT";
+ case SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS:
+ return "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS";
+ case SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL:
+ return "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL";
+ case SYSTEM_SHORTCUT_TOGGLE_TASKBAR:
+ return "SYSTEM_SHORTCUT_TOGGLE_TASKBAR";
+ case SYSTEM_SHORTCUT_TAKE_SCREENSHOT:
+ return "SYSTEM_SHORTCUT_TAKE_SCREENSHOT";
+ case SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER:
+ return "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER";
+ case SYSTEM_SHORTCUT_BRIGHTNESS_UP:
+ return "SYSTEM_SHORTCUT_BRIGHTNESS_UP";
+ case SYSTEM_SHORTCUT_BRIGHTNESS_DOWN:
+ return "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN";
+ case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP:
+ return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP";
+ case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN:
+ return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN";
+ case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE:
+ return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE";
+ case SYSTEM_SHORTCUT_VOLUME_UP:
+ return "SYSTEM_SHORTCUT_VOLUME_UP";
+ case SYSTEM_SHORTCUT_VOLUME_DOWN:
+ return "SYSTEM_SHORTCUT_VOLUME_DOWN";
+ case SYSTEM_SHORTCUT_VOLUME_MUTE:
+ return "SYSTEM_SHORTCUT_VOLUME_MUTE";
+ case SYSTEM_SHORTCUT_ALL_APPS:
+ return "SYSTEM_SHORTCUT_ALL_APPS";
+ case SYSTEM_SHORTCUT_LAUNCH_SEARCH:
+ return "SYSTEM_SHORTCUT_LAUNCH_SEARCH";
+ case SYSTEM_SHORTCUT_LANGUAGE_SWITCH:
+ return "SYSTEM_SHORTCUT_LANGUAGE_SWITCH";
+ case SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS:
+ return "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS";
+ case SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK:
+ return "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK";
+ case SYSTEM_SHORTCUT_SYSTEM_MUTE:
+ return "SYSTEM_SHORTCUT_SYSTEM_MUTE";
+ case SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION:
+ return "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION";
+ case SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS:
+ return "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS";
+ case SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT:
+ return "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT";
+ case SYSTEM_SHORTCUT_LOCK_SCREEN:
+ return "SYSTEM_SHORTCUT_LOCK_SCREEN";
+ case SYSTEM_SHORTCUT_OPEN_NOTES:
+ return "SYSTEM_SHORTCUT_OPEN_NOTES";
+ case SYSTEM_SHORTCUT_TOGGLE_POWER:
+ return "SYSTEM_SHORTCUT_TOGGLE_POWER";
+ case SYSTEM_SHORTCUT_SYSTEM_NAVIGATION:
+ return "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION";
+ case SYSTEM_SHORTCUT_SLEEP:
+ return "SYSTEM_SHORTCUT_SLEEP";
+ case SYSTEM_SHORTCUT_WAKEUP:
+ return "SYSTEM_SHORTCUT_WAKEUP";
+ case SYSTEM_SHORTCUT_MEDIA_KEY:
+ return "SYSTEM_SHORTCUT_MEDIA_KEY";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER";
+ case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS:
+ return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS";
+ case SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+ return "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+ case SYSTEM_SHORTCUT_DESKTOP_MODE:
+ return "SYSTEM_SHORTCUT_DESKTOP_MODE";
+ case SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION:
+ return "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ public KeyboardSystemShortcut(
+ @NonNull int[] keycodes,
+ int modifierState,
+ @SystemShortcut int systemShortcut) {
+ this.mKeycodes = keycodes;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mKeycodes);
+ this.mModifierState = modifierState;
+ this.mSystemShortcut = systemShortcut;
+
+ if (!(mSystemShortcut == SYSTEM_SHORTCUT_UNSPECIFIED)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_HOME)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_RECENT_APPS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_BACK)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_APP_SWITCH)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_ASSISTANT)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_TASKBAR)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_TAKE_SCREENSHOT)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_UP)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_DOWN)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_UP)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_DOWN)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_MUTE)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_ALL_APPS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SEARCH)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LANGUAGE_SWITCH)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_MUTE)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LOCK_SCREEN)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_NOTES)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_POWER)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_NAVIGATION)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_SLEEP)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_WAKEUP)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_MEDIA_KEY)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_DESKTOP_MODE)
+ && !(mSystemShortcut == SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION)) {
+ throw new java.lang.IllegalArgumentException(
+ "systemShortcut was " + mSystemShortcut + " but must be one of: "
+ + "SYSTEM_SHORTCUT_UNSPECIFIED(" + SYSTEM_SHORTCUT_UNSPECIFIED + "), "
+ + "SYSTEM_SHORTCUT_HOME(" + SYSTEM_SHORTCUT_HOME + "), "
+ + "SYSTEM_SHORTCUT_RECENT_APPS(" + SYSTEM_SHORTCUT_RECENT_APPS + "), "
+ + "SYSTEM_SHORTCUT_BACK(" + SYSTEM_SHORTCUT_BACK + "), "
+ + "SYSTEM_SHORTCUT_APP_SWITCH(" + SYSTEM_SHORTCUT_APP_SWITCH + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_ASSISTANT + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS(" + SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS + "), "
+ + "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL(" + SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL + "), "
+ + "SYSTEM_SHORTCUT_TOGGLE_TASKBAR(" + SYSTEM_SHORTCUT_TOGGLE_TASKBAR + "), "
+ + "SYSTEM_SHORTCUT_TAKE_SCREENSHOT(" + SYSTEM_SHORTCUT_TAKE_SCREENSHOT + "), "
+ + "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER(" + SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER + "), "
+ + "SYSTEM_SHORTCUT_BRIGHTNESS_UP(" + SYSTEM_SHORTCUT_BRIGHTNESS_UP + "), "
+ + "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN(" + SYSTEM_SHORTCUT_BRIGHTNESS_DOWN + "), "
+ + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP + "), "
+ + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN + "), "
+ + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE + "), "
+ + "SYSTEM_SHORTCUT_VOLUME_UP(" + SYSTEM_SHORTCUT_VOLUME_UP + "), "
+ + "SYSTEM_SHORTCUT_VOLUME_DOWN(" + SYSTEM_SHORTCUT_VOLUME_DOWN + "), "
+ + "SYSTEM_SHORTCUT_VOLUME_MUTE(" + SYSTEM_SHORTCUT_VOLUME_MUTE + "), "
+ + "SYSTEM_SHORTCUT_ALL_APPS(" + SYSTEM_SHORTCUT_ALL_APPS + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_SEARCH(" + SYSTEM_SHORTCUT_LAUNCH_SEARCH + "), "
+ + "SYSTEM_SHORTCUT_LANGUAGE_SWITCH(" + SYSTEM_SHORTCUT_LANGUAGE_SWITCH + "), "
+ + "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS(" + SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS + "), "
+ + "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK(" + SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK + "), "
+ + "SYSTEM_SHORTCUT_SYSTEM_MUTE(" + SYSTEM_SHORTCUT_SYSTEM_MUTE + "), "
+ + "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION(" + SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION + "), "
+ + "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS(" + SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS + "), "
+ + "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT(" + SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT + "), "
+ + "SYSTEM_SHORTCUT_LOCK_SCREEN(" + SYSTEM_SHORTCUT_LOCK_SCREEN + "), "
+ + "SYSTEM_SHORTCUT_OPEN_NOTES(" + SYSTEM_SHORTCUT_OPEN_NOTES + "), "
+ + "SYSTEM_SHORTCUT_TOGGLE_POWER(" + SYSTEM_SHORTCUT_TOGGLE_POWER + "), "
+ + "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION(" + SYSTEM_SHORTCUT_SYSTEM_NAVIGATION + "), "
+ + "SYSTEM_SHORTCUT_SLEEP(" + SYSTEM_SHORTCUT_SLEEP + "), "
+ + "SYSTEM_SHORTCUT_WAKEUP(" + SYSTEM_SHORTCUT_WAKEUP + "), "
+ + "SYSTEM_SHORTCUT_MEDIA_KEY(" + SYSTEM_SHORTCUT_MEDIA_KEY + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS + "), "
+ + "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
+ + "SYSTEM_SHORTCUT_DESKTOP_MODE(" + SYSTEM_SHORTCUT_DESKTOP_MODE + "), "
+ + "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION(" + SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull int[] getKeycodes() {
+ return mKeycodes;
+ }
+
+ @DataClass.Generated.Member
+ public int getModifierState() {
+ return mModifierState;
+ }
+
+ @DataClass.Generated.Member
+ public @SystemShortcut int getSystemShortcut() {
+ return mSystemShortcut;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "KeyboardSystemShortcut { " +
+ "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
+ "modifierState = " + mModifierState + ", " +
+ "systemShortcut = " + systemShortcutToString(mSystemShortcut) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(KeyboardSystemShortcut other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ KeyboardSystemShortcut that = (KeyboardSystemShortcut) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
+ && mModifierState == that.mModifierState
+ && mSystemShortcut == that.mSystemShortcut;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
+ _hash = 31 * _hash + mModifierState;
+ _hash = 31 * _hash + mSystemShortcut;
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1722890917041L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java",
+ inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final int mModifierState\nprivate final @android.hardware.input.KeyboardSystemShortcut.SystemShortcut int mSystemShortcut\npublic static final int SYSTEM_SHORTCUT_UNSPECIFIED\npublic static final int SYSTEM_SHORTCUT_HOME\npublic static final int SYSTEM_SHORTCUT_RECENT_APPS\npublic static final int SYSTEM_SHORTCUT_BACK\npublic static final int SYSTEM_SHORTCUT_APP_SWITCH\npublic static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT\npublic static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT\npublic static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS\npublic static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL\npublic static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR\npublic static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT\npublic static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER\npublic static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP\npublic static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN\npublic static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP\npublic static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN\npublic static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final int SYSTEM_SHORTCUT_VOLUME_UP\npublic static final int SYSTEM_SHORTCUT_VOLUME_DOWN\npublic static final int SYSTEM_SHORTCUT_VOLUME_MUTE\npublic static final int SYSTEM_SHORTCUT_ALL_APPS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH\npublic static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH\npublic static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS\npublic static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK\npublic static final int SYSTEM_SHORTCUT_SYSTEM_MUTE\npublic static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION\npublic static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS\npublic static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT\npublic static final int SYSTEM_SHORTCUT_LOCK_SCREEN\npublic static final int SYSTEM_SHORTCUT_OPEN_NOTES\npublic static final int SYSTEM_SHORTCUT_TOGGLE_POWER\npublic static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION\npublic static final int SYSTEM_SHORTCUT_SLEEP\npublic static final int SYSTEM_SHORTCUT_WAKEUP\npublic static final int SYSTEM_SHORTCUT_MEDIA_KEY\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER\npublic static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS\npublic static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final int SYSTEM_SHORTCUT_DESKTOP_MODE\npublic static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION\nclass KeyboardSystemShortcut extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 00ce949..de39847 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -524,19 +524,12 @@
/**
* @hide
- * The IME is active and ready with views but set invisible.
- * This flag cannot be combined with {@link #IME_VISIBLE}.
- */
- public static final int IME_INVISIBLE = 0x4;
-
- /**
- * @hide
* The IME is visible, but not yet perceptible to the user (e.g. fading in)
* by {@link android.view.WindowInsetsController}.
*
* @see InputMethodManager#reportPerceptible
*/
- public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8;
+ public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4;
// Min and max values for back disposition.
private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
@@ -2796,6 +2789,9 @@
* <p>This dismisses the {@link #getStylusHandwritingWindow ink window} and stops intercepting
* stylus {@code MotionEvent}s.
*
+ * <p>Connectionless handwriting sessions should be finished using {@link
+ * #finishConnectionlessStylusHandwriting(CharSequence)}.
+ *
* <p>Note for IME developers: Call this method at any time to finish the current handwriting
* session. Generally, this should be invoked after a short timeout, giving the user enough time
* to start the next stylus stroke, if any. By default, system will time-out after few seconds.
@@ -2803,9 +2799,6 @@
*
* <p>Handwriting session will be finished by framework on next {@link #onFinishInput()}.
*/
- // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
- // <p>Connectionless handwriting sessions should be finished using {@link
- // #finishConnectionlessStylusHandwriting(CharSequence)}.
public final void finishStylusHandwriting() {
if (DEBUG) Log.v(TAG, "finishStylusHandwriting()");
if (mInkWindow == null) {
@@ -3125,7 +3118,7 @@
mInShowWindow = true;
final int previousImeWindowStatus =
(mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
- ? (!mWindowVisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
+ ? (!mWindowVisible ? -1 : IME_VISIBLE) : 0);
startViews(prepareWindow(showInput));
final int nextImeWindowStatus = mapToImeWindowStatus();
if (previousImeWindowStatus != nextImeWindowStatus) {
diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
index 540243c..bfa95f7 100644
--- a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
+++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java
@@ -22,6 +22,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -126,15 +127,29 @@
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- if (mCode != KEYCODE_UNKNOWN) {
+ if (isClickable()) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
if (isLongClickable()) {
info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+ new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK,
+ getAccessibilityLongClickActionLabel()));
}
}
}
+ /**
+ * Gets the accessibility long click action label for the button, or {@code null} for no label.
+ */
+ @Nullable
+ private CharSequence getAccessibilityLongClickActionLabel() {
+ if (Flags.imeSwitcherRevamp()
+ && getId() == com.android.internal.R.id.input_method_nav_ime_switcher) {
+ return getContext().getText(
+ com.android.internal.R.string.input_method_ime_switch_long_click_action_desc);
+ }
+ return null;
+ }
+
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
diff --git a/core/java/android/os/ExternalVibrationScale.aidl b/core/java/android/os/ExternalVibrationScale.aidl
index cf6f8ed..644bece 100644
--- a/core/java/android/os/ExternalVibrationScale.aidl
+++ b/core/java/android/os/ExternalVibrationScale.aidl
@@ -33,12 +33,24 @@
SCALE_VERY_HIGH = 2
}
+ // TODO(b/345186129): remove this once we finish migrating to scale factor.
/**
* The scale level that will be applied to external vibrations.
*/
ScaleLevel scaleLevel = ScaleLevel.SCALE_NONE;
/**
+ * The scale factor that will be applied to external vibrations.
+ *
+ * Values in (0,1) will scale down the vibrations, values > 1 will scale up vibrations within
+ * hardware limits. A zero scale factor indicates the external vibration should be muted.
+ *
+ * TODO(b/345186129): update this once we finish migrating, negative should not be expected.
+ * Negative values should be ignored in favour of the legacy ScaleLevel.
+ */
+ float scaleFactor = -1f; // undefined
+
+ /**
* The adaptive haptics scale that will be applied to external vibrations.
*/
float adaptiveHapticsScale = 1f;
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 97993b6..6aa9852 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -42,4 +42,12 @@
// vibrate/isVibrating/cancel.
oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant,
String reason, int flags, int privFlags);
+
+ // Similar to performHapticFeedback but the effect is customized to the input device. The
+ // customization for each constant is defined on a device basis, and the behavior will be the
+ // same as performHapticFeedback when no customization is provided for a given constant and
+ // device.
+ oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
+ int constant, int inputDeviceId, int inputSource, String reason, int flags,
+ int privFlags);
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 5339d73..011a3ee 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -215,6 +215,17 @@
}
@Override
+ public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+ int inputSource, String reason, int flags, int privFlags) {
+ if (mVibratorManager == null) {
+ Log.w(TAG, "Failed to perform haptic feedback for input device; no vibrator manager.");
+ return;
+ }
+ mVibratorManager.performHapticFeedbackForInputDevice(constant, inputDeviceId, inputSource,
+ reason, flags, privFlags);
+ }
+
+ @Override
public void cancel() {
if (mVibratorManager == null) {
Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index a9846ba..58ab5b6 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -161,6 +161,22 @@
}
@Override
+ public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+ int inputSource, String reason, int flags, int privFlags) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to perform haptic feedback for input device;"
+ + " no vibrator manager service.");
+ return;
+ }
+ try {
+ mService.performHapticFeedbackForInputDevice(mUid, mContext.getDeviceId(), mPackageName,
+ constant, inputDeviceId, inputSource, reason, flags, privFlags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to perform haptic feedback for input device.", e);
+ }
+ }
+
+ @Override
public void cancel() {
cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 06c516a..28f2c25 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4824,6 +4824,7 @@
* <p>Note that this does not alter the user's pre-existing user restrictions.
*
* @param userId the id of the user to become admin
+ * @throws SecurityException if changing ADMIN status of the user is not allowed
* @hide
*/
@RequiresPermission(allOf = {
@@ -4844,6 +4845,7 @@
* <p>Note that this does not alter the user's pre-existing user restrictions.
*
* @param userId the id of the user to revoke admin rights from
+ * @throws SecurityException if changing ADMIN status of the user is not allowed
* @hide
*/
@RequiresPermission(allOf = {
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index f3ef9e1..e68b746 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -663,6 +663,15 @@
* @hide
*/
public static float scale(float intensity, float scaleFactor) {
+ if (Flags.hapticsScaleV2Enabled()) {
+ if (Float.compare(scaleFactor, 1) <= 0 || Float.compare(intensity, 0) == 0) {
+ // Scaling down or scaling zero intensity is straightforward.
+ return scaleFactor * intensity;
+ }
+ // Using S * x / (1 + (S - 1) * x^2) as the scale up function to converge to 1.0.
+ return (scaleFactor * intensity) / (1 + (scaleFactor - 1) * intensity * intensity);
+ }
+
// Applying gamma correction to the scale factor, which is the same as encoding the input
// value, scaling it, then decoding the scaled value.
float scale = MathUtils.pow(scaleFactor, 1f / SCALE_GAMMA);
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 161cce0..36233b7 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -553,6 +553,31 @@
}
/**
+ * Performs a haptic feedback. Similar to {@link #performHapticFeedback} but also take into the
+ * consideration the {@link InputDevice} that triggered the haptic
+ *
+ * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via
+ * the {@code constant}, which should be one of the effect constants provided in
+ * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is
+ * consistent across all usages on the same device.
+ *
+ * @param constant the ID for the haptic feedback. This should be one of the constants
+ * defined in {@link HapticFeedbackConstants}.
+ * @param inputDeviceId the integer id of the input device that triggered the haptic feedback.
+ * @param inputSource the {@link InputDevice.Source} that triggered the haptic feedback.
+ * @param reason the reason for this haptic feedback.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
+ * @hide
+ */
+ public void performHapticFeedbackForInputDevice(
+ int constant, int inputDeviceId, int inputSource, String reason,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
+ Log.w(TAG, "performHapticFeedbackForInputDevice is not supported");
+ }
+
+ /**
* Query whether the vibrator natively supports the given effects.
*
* <p>If an effect is not supported, the system may still automatically fall back to playing
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 2c7a852..0428876 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -155,6 +155,27 @@
}
/**
+ * Performs a haptic feedback. Similar to {@link #performHapticFeedback} but also take input
+ * into consideration.
+ *
+ * @param constant the ID of the requested haptic feedback. Should be one of the constants
+ * defined in {@link HapticFeedbackConstants}.
+ * @param inputDeviceId the integer id of the input device that customizes the haptic feedback
+ * corresponding to the {@code constant}.
+ * @param inputSource the {@link InputDevice.Source} that customizes the haptic feedback
+ * corresponding to the {@code constant}.
+ * @param reason the reason for this haptic feedback.
+ * @param flags Additional flags as per {@link HapticFeedbackConstants}.
+ * @param privFlags Additional private flags as per {@link HapticFeedbackConstants}.
+ * @hide
+ */
+ public void performHapticFeedbackForInputDevice(int constant, int inputDeviceId,
+ int inputSource, String reason, @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
+ Log.w(TAG, "performHapticFeedbackForInputDevice is not supported");
+ }
+
+ /**
* Turn all the vibrators off.
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index a4164e9..e6e5a27 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -49,8 +49,22 @@
*/
public class VibrationConfig {
+ /**
+ * Hardcoded default scale level gain to be applied between each scale level to define their
+ * scale factor value.
+ *
+ * <p>Default gain defined as 3 dBs.
+ */
+ private static final float DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
+
+ /**
+ * Hardcoded default amplitude to be used when device config is invalid, i.e. not in [1,255].
+ */
+ private static final int DEFAULT_AMPLITUDE = 255;
+
// TODO(b/191150049): move these to vibrator static config file
private final float mHapticChannelMaxVibrationAmplitude;
+ private final int mDefaultVibrationAmplitude;
private final int mRampStepDurationMs;
private final int mRampDownDurationMs;
private final int mRequestVibrationParamsTimeoutMs;
@@ -75,8 +89,10 @@
/** @hide */
public VibrationConfig(@Nullable Resources resources) {
+ mDefaultVibrationAmplitude = resources.getInteger(
+ com.android.internal.R.integer.config_defaultVibrationAmplitude);
mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
- com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude, 0);
+ com.android.internal.R.dimen.config_hapticChannelMaxVibrationAmplitude);
mRampDownDurationMs = loadInteger(resources,
com.android.internal.R.integer.config_vibrationWaveformRampDownDuration, 0);
mRampStepDurationMs = loadInteger(resources,
@@ -87,9 +103,9 @@
com.android.internal.R.array.config_requestVibrationParamsForUsages);
mIgnoreVibrationsOnWirelessCharger = loadBoolean(resources,
- com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
+ com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger);
mKeyboardVibrationSettingsSupported = loadBoolean(resources,
- com.android.internal.R.bool.config_keyboardVibrationSettingsSupported, false);
+ com.android.internal.R.bool.config_keyboardVibrationSettingsSupported);
mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -115,16 +131,16 @@
return value;
}
- private static float loadFloat(@Nullable Resources res, int resId, float defaultValue) {
- return res != null ? res.getFloat(resId) : defaultValue;
+ private static float loadFloat(@Nullable Resources res, int resId) {
+ return res != null ? res.getFloat(resId) : 0f;
}
private static int loadInteger(@Nullable Resources res, int resId, int defaultValue) {
return res != null ? res.getInteger(resId) : defaultValue;
}
- private static boolean loadBoolean(@Nullable Resources res, int resId, boolean defaultValue) {
- return res != null ? res.getBoolean(resId) : defaultValue;
+ private static boolean loadBoolean(@Nullable Resources res, int resId) {
+ return res != null && res.getBoolean(resId);
}
private static int[] loadIntArray(@Nullable Resources res, int resId) {
@@ -145,6 +161,26 @@
}
/**
+ * Return the device default vibration amplitude value to replace the
+ * {@link android.os.VibrationEffect#DEFAULT_AMPLITUDE} constant.
+ */
+ public int getDefaultVibrationAmplitude() {
+ if (mDefaultVibrationAmplitude < 1 || mDefaultVibrationAmplitude > 255) {
+ return DEFAULT_AMPLITUDE;
+ }
+ return mDefaultVibrationAmplitude;
+ }
+
+ /**
+ * Return the device default gain to be applied between scale levels to define the scale factor
+ * for each level.
+ */
+ public float getDefaultVibrationScaleLevelGain() {
+ // TODO(b/356407380): add device config for this
+ return DEFAULT_SCALE_LEVEL_GAIN;
+ }
+
+ /**
* The duration, in milliseconds, that should be applied to the ramp to turn off the vibrator
* when a vibration is cancelled or finished at non-zero amplitude.
*/
@@ -233,6 +269,7 @@
public String toString() {
return "VibrationConfig{"
+ "mIgnoreVibrationsOnWirelessCharger=" + mIgnoreVibrationsOnWirelessCharger
+ + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
+ ", mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude
+ ", mRampStepDurationMs=" + mRampStepDurationMs
+ ", mRampDownDurationMs=" + mRampDownDurationMs
@@ -258,6 +295,7 @@
pw.println("VibrationConfig:");
pw.increaseIndent();
pw.println("ignoreVibrationsOnWirelessCharger = " + mIgnoreVibrationsOnWirelessCharger);
+ pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
pw.println("rampStepDurationMs = " + mRampStepDurationMs);
pw.println("rampDownDurationMs = " + mRampDownDurationMs);
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 67c3464..53a1a67d 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -3,13 +3,6 @@
flag {
namespace: "haptics"
- name: "use_vibrator_haptic_feedback"
- description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session"
- bug: "295459081"
-}
-
-flag {
- namespace: "haptics"
name: "haptic_feedback_vibration_oem_customization_enabled"
description: "Enables OEMs/devices to customize vibrations for haptic feedback"
# Make read only. This is because the flag is used only once, and this could happen before
@@ -20,13 +13,6 @@
flag {
namespace: "haptics"
- name: "keyboard_category_enabled"
- description: "Enables the independent keyboard vibration settings feature"
- bug: "289107579"
-}
-
-flag {
- namespace: "haptics"
name: "adaptive_haptics_enabled"
description: "Enables the adaptive haptics feature"
bug: "305961689"
@@ -85,3 +71,45 @@
purpose: PURPOSE_FEATURE
}
}
+
+flag {
+ namespace: "haptics"
+ name: "fix_audio_coupled_haptics_scaling"
+ description: "Fix the audio-coupled haptics scaling to use same function as VibrationEffect"
+ bug: "356144312"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "haptics"
+ name: "load_haptic_feedback_vibration_customization_from_resources"
+ description: "Load haptic feedback vibrations customization from resources."
+ is_fixed_read_only: true
+ bug: "295142743"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
+
+flag {
+ namespace: "haptics"
+ name: "haptic_feedback_input_source_customization_enabled"
+ description: "Enabled the extended haptic feedback customization by input source."
+ bug: "331819348"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
+
+flag {
+ namespace: "haptics"
+ name: "haptics_scale_v2_enabled"
+ description: "Enables new haptics scaling function across all usages"
+ bug: "345186129"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index a26c6f4..a95ce79 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -104,7 +104,7 @@
public static void serialize(@NonNull VibrationEffect effect, @NonNull Writer writer,
@Flags int flags) throws IOException {
// Serialize effect first to fail early.
- XmlSerializedVibration<VibrationEffect> serializedVibration =
+ XmlSerializedVibration<? extends VibrationEffect> serializedVibration =
toSerializedVibration(effect, flags);
TypedXmlSerializer xmlSerializer = Xml.newFastSerializer();
xmlSerializer.setFeature(XML_FEATURE_INDENT_OUTPUT, (flags & FLAG_PRETTY_PRINT) != 0);
@@ -114,9 +114,9 @@
xmlSerializer.endDocument();
}
- private static XmlSerializedVibration<VibrationEffect> toSerializedVibration(
+ private static XmlSerializedVibration<? extends VibrationEffect> toSerializedVibration(
VibrationEffect effect, @Flags int flags) throws SerializationFailedException {
- XmlSerializedVibration<VibrationEffect> serializedVibration;
+ XmlSerializedVibration<? extends VibrationEffect> serializedVibration;
int serializerFlags = 0;
if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
serializerFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5703f69..7ca40ea 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2287,6 +2287,26 @@
"android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS";
/**
+ * Activity Action: Show Other NFC services settings.
+ * <p>
+ * If a Settings activity handles this intent action, an "Other NFC services" entry will be
+ * shown in the Default payment app settings, and clicking it will launch that activity.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_ACTION_MANAGE_SERVICES_SETTINGS)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS =
+ "android.settings.MANAGE_OTHER_NFC_SERVICES_SETTINGS";
+
+ /**
* Activity Action: Show app screen size list settings for user to override app aspect
* ratio.
* <p>
@@ -12543,6 +12563,19 @@
"launcher_taskbar_education_showing";
/**
+ * Whether any Compat UI Education is currently showing.
+ *
+ * <p>1 if true, 0 or unset otherwise.
+ *
+ * <p>This setting is used to inform other components that the Compat UI Education is
+ * currently showing, which can prevent them from showing something else to the user.
+ *
+ * @hide
+ */
+ public static final String COMPAT_UI_EDUCATION_SHOWING =
+ "compat_ui_education_showing";
+
+ /**
* Whether or not adaptive charging feature is enabled by user.
* Type: int (0 for false, 1 for true)
* Default: 1
@@ -20158,6 +20191,36 @@
*/
public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11;
+ /**
+ * Phone switching request source
+ * @hide
+ */
+ public static final String PHONE_SWITCHING_REQUEST_SOURCE =
+ "phone_switching_request_source";
+
+ /**
+ * No phone switching request source
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_REQUEST_SOURCE_NONE = 0;
+
+ /**
+ * Phone switching triggered by watch
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_REQUEST_SOURCE_WATCH = 1;
+
+ /**
+ * Phone switching triggered by companion, user confirmation required
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_REQUEST_SOURCE_COMPANION_USER_CONFIRMATION = 2;
+
+ /**
+ * Phone switching triggered by companion, user confirmation not required
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_REQUEST_SOURCE_COMPANION = 3;
/**
* Whether the device has enabled the feature to reduce motion and animation
@@ -20205,14 +20268,6 @@
public static final int TETHERED_CONFIG_RESTRICTED = 3;
/**
- * Whether phone switching is supported.
- *
- * (0 = false, 1 = true)
- * @hide
- */
- public static final String PHONE_SWITCHING_SUPPORTED = "phone_switching_supported";
-
- /**
* Setting indicating the name of the Wear OS package that hosts the Media Controls UI.
*
* @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d019bad..6eaef78f 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4985,6 +4985,16 @@
*/
public static final String COLUMN_SATELLITE_ESOS_SUPPORTED = "satellite_esos_supported";
+ /**
+ * TelephonyProvider column name for satellite provisioned status. The value of this
+ * column is set based on whether carrier roaming or OEM-enabled NB-IOT satellite service is
+ * provisioned or not. By default, it's disabled.
+ *
+ * @hide
+ */
+ public static final String COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM =
+ "is_satellite_provisioned_for_non_ip_datagram";
+
/** All columns in {@link SimInfo} table. */
private static final List<String> ALL_COLUMNS = List.of(
COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5061,7 +5071,8 @@
COLUMN_TRANSFER_STATUS,
COLUMN_SATELLITE_ENTITLEMENT_STATUS,
COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
- COLUMN_SATELLITE_ESOS_SUPPORTED
+ COLUMN_SATELLITE_ESOS_SUPPORTED,
+ COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM
);
/**
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 17d2790..013ec5f 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -28,7 +28,9 @@
import android.util.Log;
import android.view.WindowManager;
+import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
@@ -52,43 +54,51 @@
// An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
// requests to the {@link DreamOverlayService}
private static class OverlayClient extends IDreamOverlayClient.Stub {
- private final DreamOverlayService mService;
+ private final WeakReference<DreamOverlayService> mService;
private boolean mShowComplications;
private ComponentName mDreamComponent;
IDreamOverlayCallback mDreamOverlayCallback;
- OverlayClient(DreamOverlayService service) {
+ OverlayClient(WeakReference<DreamOverlayService> service) {
mService = service;
}
+ private void applyToDream(Consumer<DreamOverlayService> consumer) {
+ final DreamOverlayService service = mService.get();
+
+ if (service != null) {
+ consumer.accept(service);
+ }
+ }
+
@Override
public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
String dreamComponent, boolean shouldShowComplications) throws RemoteException {
mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
mShowComplications = shouldShowComplications;
mDreamOverlayCallback = callback;
- mService.startDream(this, params);
+ applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
}
@Override
public void wakeUp() {
- mService.wakeUp(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.wakeUp(this));
}
@Override
public void endDream() {
- mService.endDream(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.endDream(this));
}
@Override
public void comeToFront() {
- mService.comeToFront(this);
+ applyToDream(dreamOverlayService -> dreamOverlayService.comeToFront(this));
}
@Override
public void onWakeRequested() {
if (Flags.dreamWakeRedirect()) {
- mService.onWakeRequested();
+ applyToDream(DreamOverlayService::onWakeRequested);
}
}
@@ -161,17 +171,24 @@
});
}
- private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+ private static class DreamOverlay extends IDreamOverlay.Stub {
+ private final WeakReference<DreamOverlayService> mService;
+
+ DreamOverlay(DreamOverlayService service) {
+ mService = new WeakReference<>(service);
+ }
+
@Override
public void getClient(IDreamOverlayClientCallback callback) {
try {
- callback.onDreamOverlayClient(
- new OverlayClient(DreamOverlayService.this));
+ callback.onDreamOverlayClient(new OverlayClient(mService));
} catch (RemoteException e) {
Log.e(TAG, "could not send client to callback", e);
}
}
- };
+ }
+
+ private final IDreamOverlay mDreamOverlay = new DreamOverlay(this);
public DreamOverlayService() {
}
@@ -195,6 +212,12 @@
}
}
+ @Override
+ public void onDestroy() {
+ mCurrentClient = null;
+ super.onDestroy();
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
index 5194cdd..2472860 100644
--- a/core/java/android/service/notification/DeviceEffectsApplier.java
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -16,8 +16,6 @@
package android.service.notification;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
-
/**
* Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
* make sense for the current platform.
@@ -43,5 +41,5 @@
* changing as a result of an explicit user action, then it makes sense to
* apply them immediately regardless.
*/
- void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source);
+ void apply(ZenDeviceEffects effects, @ZenModeConfig.ConfigOrigin int source);
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 7ca248d..224379b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -25,6 +25,8 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
@@ -112,68 +114,83 @@
private static final String TAG = "ZenModeConfig";
/**
- * The {@link ZenModeConfig} is being updated because of an unknown reason.
+ * The {@link ZenModeConfig} is updated because of an unknown reason.
*/
- public static final int UPDATE_ORIGIN_UNKNOWN = 0;
+ public static final int ORIGIN_UNKNOWN = 0;
/**
- * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from
+ * The {@link ZenModeConfig} is updated because of system initialization (i.e. load from
* storage, on device boot).
*/
- public static final int UPDATE_ORIGIN_INIT = 1;
+ public static final int ORIGIN_INIT = 1;
- /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */
- public static final int UPDATE_ORIGIN_INIT_USER = 2;
+ /** The {@link ZenModeConfig} is updated (replaced) because of a user switch or unlock. */
+ public static final int ORIGIN_INIT_USER = 2;
- /** The {@link ZenModeConfig} is being updated because of a user action, for example:
+ /**
+ * The {@link ZenModeConfig} is updated because of a <em>user action</em> performed from a
+ * system surface, such as:
* <ul>
- * <li>{@link NotificationManager#setAutomaticZenRuleState} with a
- * {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
- * <li>Adding, updating, or removing a rule from Settings.</li>
- * <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g.
- * Quick Settings).</li>
+ * <li>Adding, updating, or removing a rule from Settings.
+ * <li>Activating or deactivating a rule through the System (e.g. from Settings/Modes).
+ * <li>Activating or deactivating a rule through SystemUi (e.g. with Quick Settings).
* </ul>
+ *
+ * <p>This does <em>not</em> include user actions from apps ({@link #ORIGIN_USER_IN_APP} nor
+ * non-user actions from the system ({@link #ORIGIN_SYSTEM}).
*/
- public static final int UPDATE_ORIGIN_USER = 3;
+ public static final int ORIGIN_USER_IN_SYSTEMUI = 3;
/**
- * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of
- * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on
- * a previously set schedule).
+ * The {@link ZenModeConfig} is updated by an app, but (probably) not as a result of a user
+ * action (for example, activating an {@link AutomaticZenRule} based on a previously set
+ * schedule).
+ *
+ * <p>Note that {@code ORIGIN_APP} is the only option for all public APIs except
+ * {@link NotificationManager#setAutomaticZenRuleState} -- apps cannot claim to be adding or
+ * updating a rule on behalf of the user.
*/
- public static final int UPDATE_ORIGIN_APP = 4;
+ public static final int ORIGIN_APP = 4;
/**
- * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only
- * includes cases where the call is coming from the System/SystemUI but the change is not due to
- * a user action (e.g. automatically activating a schedule-based rule). If the change is a
- * result of a user action (e.g. activating a rule by tapping on its QS tile) then
- * {@link #UPDATE_ORIGIN_USER} is used instead.
+ * The {@link ZenModeConfig} is updated by the System (or SystemUI). This only includes cases
+ * where the call is coming from the System/SystemUI but the change is not due to a user action
+ * (e.g. automatically activating a schedule-based rule, or some service toggling Do Not
+ * Disturb). See {@link #ORIGIN_USER_IN_SYSTEMUI}.
*/
- public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5;
+ public static final int ORIGIN_SYSTEM = 5;
/**
* The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration
* is being restored from a backup.
*/
- public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6;
+ public static final int ORIGIN_RESTORE_BACKUP = 6;
- @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = {
- UPDATE_ORIGIN_UNKNOWN,
- UPDATE_ORIGIN_INIT,
- UPDATE_ORIGIN_INIT_USER,
- UPDATE_ORIGIN_USER,
- UPDATE_ORIGIN_APP,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- UPDATE_ORIGIN_RESTORE_BACKUP
+ /**
+ * The {@link ZenModeConfig} is updated from an app, and the app reports it's the result
+ * of a user action (e.g. tapping a button in the Wellbeing App to start Bedtime Mode).
+ * Corresponds to {@link NotificationManager#setAutomaticZenRuleState} with a
+ * {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li>
+ */
+ public static final int ORIGIN_USER_IN_APP = 7;
+
+ @IntDef(prefix = { "ORIGIN_" }, value = {
+ ORIGIN_UNKNOWN,
+ ORIGIN_INIT,
+ ORIGIN_INIT_USER,
+ ORIGIN_USER_IN_SYSTEMUI,
+ ORIGIN_APP,
+ ORIGIN_SYSTEM,
+ ORIGIN_RESTORE_BACKUP,
+ ORIGIN_USER_IN_APP
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ConfigChangeOrigin {}
+ public @interface ConfigOrigin {}
public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED;
- public static final int MAX_SOURCE = SOURCE_STAR;
+ private static final int MAX_SOURCE = SOURCE_STAR;
private static final int DEFAULT_SOURCE = SOURCE_STAR;
private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
@@ -439,7 +456,7 @@
newRule.conditionId = Uri.EMPTY;
newRule.allowManualInvocation = true;
newRule.zenPolicy = getDefaultZenPolicy();
- newRule.pkg = "android";
+ newRule.pkg = PACKAGE_ANDROID;
manualRule = newRule;
}
}
@@ -942,15 +959,9 @@
rt.user = safeInt(parser, ZEN_ATT_USER, rt.user);
boolean readSuppressedEffects = false;
boolean readManualRule = false;
+ boolean readManualRuleWithoutPolicy = false;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
- if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
- if (Flags.modesUi() && !readManualRule) {
- // migrate from fields on config into manual rule
- rt.manualRule.zenPolicy = rt.toZenPolicy();
- }
- return rt;
- }
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
@@ -1019,9 +1030,17 @@
rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS,
DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
} else if (MANUAL_TAG.equals(tag)) {
- rt.manualRule = readRuleXml(parser);
- if (rt.manualRule != null) {
+ ZenRule manualRule = readRuleXml(parser);
+ if (manualRule != null) {
+ rt.manualRule = manualRule;
+
+ // Manual rule may be present prior to modes_ui if it were on, but in that
+ // case it would not have a set policy, so make note of the need to set
+ // it up later.
readManualRule = true;
+ if (rt.manualRule.zenPolicy == null) {
+ readManualRuleWithoutPolicy = true;
+ }
}
} else if (AUTOMATIC_TAG.equals(tag)
|| (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
@@ -1043,6 +1062,23 @@
STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
}
}
+ if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+ if (Flags.modesUi() && (!readManualRule || readManualRuleWithoutPolicy)) {
+ // migrate from fields on config into manual rule
+ rt.manualRule.zenPolicy = rt.toZenPolicy();
+ if (readManualRuleWithoutPolicy) {
+ // indicates that the xml represents a pre-modes_ui XML with an enabled
+ // manual rule; set rule active, and fill in other fields as would be done
+ // in ensureManualZenRule() and setManualZenMode().
+ rt.manualRule.pkg = PACKAGE_ANDROID;
+ rt.manualRule.type = AutomaticZenRule.TYPE_OTHER;
+ rt.manualRule.condition = new Condition(
+ rt.manualRule.conditionId != null ? rt.manualRule.conditionId
+ : Uri.EMPTY, "", STATE_TRUE);
+ }
+ }
+ return rt;
+ }
}
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
@@ -1174,7 +1210,7 @@
}
if (Flags.modesUi()) {
rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN,
- UPDATE_ORIGIN_UNKNOWN);
+ ORIGIN_UNKNOWN);
rt.legacySuppressedEffects = safeInt(parser,
RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0);
}
@@ -2537,7 +2573,8 @@
@ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields;
@Nullable public Instant deletionInstant; // Only set on deleted rules.
@FlaggedApi(Flags.FLAG_MODES_UI)
- @ConfigChangeOrigin public int disabledOrigin = UPDATE_ORIGIN_UNKNOWN;
+ @ConfigOrigin
+ public int disabledOrigin = ORIGIN_UNKNOWN;
// The obsolete suppressed effects in NM.Policy (SCREEN_ON, SCREEN_OFF) cannot be put in a
// ZenPolicy, so we store them here, only for the manual rule.
@FlaggedApi(Flags.FLAG_MODES_UI)
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 88a1b9c..4d176f2 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -118,6 +118,13 @@
}
flag {
+ name: "insert_mode_highlight_range"
+ namespace: "text"
+ description: "Make the highlight range stick after editing, this handles the corner cases where the entire text is replaced with itself(or transformed by developer) after each editing."
+ bug: "355137282"
+}
+
+flag {
name: "insert_mode_not_update_selection"
namespace: "text"
description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
@@ -259,4 +266,24 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "handwriting_gesture_with_transformation"
+ namespace: "text"
+ description: "Fix handwriting gesture is not working when view has transformation."
+ bug: "342619429"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "typeface_cache_for_var_settings"
+ namespace: "text"
+ description: "Cache Typeface instance for font variation settings."
+ bug: "355462362"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
index 59b80f3..6c6576f 100644
--- a/core/java/android/text/method/InsertModeTransformationMethod.java
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -36,6 +36,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import java.lang.reflect.Array;
@@ -171,9 +172,15 @@
// The text change is before the highlight start, move the highlight start.
mStart += diff;
} else {
- // The text change covers the highlight start. Extend the highlight start to the
- // change start. This should be a rare case.
- mStart = start;
+ if (Flags.insertModeHighlightRange()) {
+ // The text change covers the highlight start. Don't change the start except
+ // when it's out of range.
+ mStart = Math.min(mStart, s.length());
+ } else {
+ // The text change covers the highlight start. Extend the highlight start to the
+ // change start. This should be a rare case.
+ mStart = start;
+ }
}
}
@@ -181,9 +188,15 @@
// The text change is before the highlight end, move the highlight end.
mEnd += diff;
} else if (start < mEnd) {
- // The text change covers the highlight end. Extend the highlight end to the
- // change end. This should be a rare case.
- mEnd = start + count;
+ if (Flags.insertModeHighlightRange()) {
+ // The text change covers the highlight end. Don't change the end except when it's
+ // out of range.
+ mEnd = Math.min(mEnd, s.length());
+ } else {
+ // The text change covers the highlight end. Extend the highlight end to the
+ // change end. This should be a rare case.
+ mEnd = start + count;
+ }
}
}
diff --git a/core/java/android/text/style/AccessibilityClickableSpan.java b/core/java/android/text/style/AccessibilityClickableSpan.java
index 534ce63..ee8d156 100644
--- a/core/java/android/text/style/AccessibilityClickableSpan.java
+++ b/core/java/android/text/style/AccessibilityClickableSpan.java
@@ -156,4 +156,12 @@
return new AccessibilityClickableSpan[size];
}
};
+
+ /**
+ * @return the ID of the original clickable span that this is applied to.
+ * @hide
+ */
+ public int getOriginalClickableSpanId() {
+ return mOriginalClickableSpanId;
+ }
}
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index b3e7bda..f70e6c5 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -119,7 +119,10 @@
this(gapWidth, color, true, bulletRadius);
}
- private BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
+ /**
+ * @hide
+ */
+ public BulletSpan(int gapWidth, @ColorInt int color, boolean wantColor,
@IntRange(from = 0) int bulletRadius) {
mGapWidth = gapWidth;
mBulletRadius = bulletRadius;
@@ -199,6 +202,14 @@
return mColor;
}
+ /**
+ * @return true if the bullet should apply the color.
+ * @hide
+ */
+ public boolean getWantColor() {
+ return mWantColor;
+ }
+
@Override
public void drawLeadingMargin(@NonNull Canvas canvas, @NonNull Paint paint, int x, int dir,
int top, int baseline, int bottom,
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index ad044af..0cf96f6 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -248,21 +248,42 @@
}
public SuggestionSpan(Parcel src) {
- mSuggestions = src.readStringArray();
- mFlags = src.readInt();
- mLocaleStringForCompatibility = src.readString();
- mLanguageTag = src.readString();
- mHashCode = src.readInt();
- mEasyCorrectUnderlineColor = src.readInt();
- mEasyCorrectUnderlineThickness = src.readFloat();
- mMisspelledUnderlineColor = src.readInt();
- mMisspelledUnderlineThickness = src.readFloat();
- mAutoCorrectionUnderlineColor = src.readInt();
- mAutoCorrectionUnderlineThickness = src.readFloat();
- mGrammarErrorUnderlineColor = src.readInt();
- mGrammarErrorUnderlineThickness = src.readFloat();
+ this(/* suggestions= */ src.readStringArray(), /* flags= */ src.readInt(),
+ /* localStringForCompatibility= */ src.readString(),
+ /* languageTag= */ src.readString(), /* hashCode= */ src.readInt(),
+ /* easyCorrectUnderlineColor= */ src.readInt(),
+ /* easyCorrectUnderlineThickness= */ src.readFloat(),
+ /* misspelledUnderlineColor= */ src.readInt(),
+ /* misspelledUnderlineThickness= */ src.readFloat(),
+ /* autoCorrectionUnderlineColor= */ src.readInt(),
+ /* autoCorrectionUnderlineThickness= */ src.readFloat(),
+ /* grammarErrorUnderlineColor= */ src.readInt(),
+ /* grammarErrorUnderlineThickness= */ src.readFloat());
}
+ /** @hide */
+ public SuggestionSpan(String[] suggestions, int flags, String localeStringForCompatibility,
+ String languageTag, int hashCode, int easyCorrectUnderlineColor,
+ float easyCorrectUnderlineThickness, int misspelledUnderlineColor,
+ float misspelledUnderlineThickness, int autoCorrectionUnderlineColor,
+ float autoCorrectionUnderlineThickness, int grammarErrorUnderlineColor,
+ float grammarErrorUnderlineThickness) {
+ mSuggestions = suggestions;
+ mFlags = flags;
+ mLocaleStringForCompatibility = localeStringForCompatibility;
+ mLanguageTag = languageTag;
+ mHashCode = hashCode;
+ mEasyCorrectUnderlineColor = easyCorrectUnderlineColor;
+ mEasyCorrectUnderlineThickness = easyCorrectUnderlineThickness;
+ mMisspelledUnderlineColor = misspelledUnderlineColor;
+ mMisspelledUnderlineThickness = misspelledUnderlineThickness;
+ mAutoCorrectionUnderlineColor = autoCorrectionUnderlineColor;
+ mAutoCorrectionUnderlineThickness = autoCorrectionUnderlineThickness;
+ mGrammarErrorUnderlineColor = grammarErrorUnderlineColor;
+ mGrammarErrorUnderlineThickness = grammarErrorUnderlineThickness;
+ }
+
+
/**
* @return an array of suggestion texts for this span
*/
@@ -452,4 +473,44 @@
public void notifySelection(Context context, String original, int index) {
Log.w(TAG, "notifySelection() is deprecated. Does nothing.");
}
+
+ /** @hide */
+ public float getEasyCorrectUnderlineThickness() {
+ return mEasyCorrectUnderlineThickness;
+ }
+
+ /** @hide */
+ public int getEasyCorrectUnderlineColor() {
+ return mEasyCorrectUnderlineColor;
+ }
+
+ /** @hide */
+ public float getMisspelledUnderlineThickness() {
+ return mMisspelledUnderlineThickness;
+ }
+
+ /** @hide */
+ public int getMisspelledUnderlineColor() {
+ return mMisspelledUnderlineColor;
+ }
+
+ /** @hide */
+ public float getAutoCorrectionUnderlineThickness() {
+ return mAutoCorrectionUnderlineThickness;
+ }
+
+ /** @hide */
+ public int getAutoCorrectionUnderlineColor() {
+ return mAutoCorrectionUnderlineColor;
+ }
+
+ /** @hide */
+ public float getGrammarErrorUnderlineThickness() {
+ return mGrammarErrorUnderlineThickness;
+ }
+
+ /** @hide */
+ public int getGrammarErrorUnderlineColor() {
+ return mGrammarErrorUnderlineColor;
+ }
}
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index d61228b..245a9db 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -233,36 +233,59 @@
}
public TextAppearanceSpan(Parcel src) {
- mFamilyName = src.readString();
- mStyle = src.readInt();
- mTextSize = src.readInt();
- if (src.readInt() != 0) {
- mTextColor = ColorStateList.CREATOR.createFromParcel(src);
- } else {
- mTextColor = null;
- }
- if (src.readInt() != 0) {
- mTextColorLink = ColorStateList.CREATOR.createFromParcel(src);
- } else {
- mTextColorLink = null;
- }
- mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
+ this(/* familyName= */ src.readString(),
+ /* style= */ src.readInt(),
+ /* textSize= */ src.readInt(),
+ /* textColor= */ (src.readInt() != 0)
+ ? ColorStateList.CREATOR.createFromParcel(src) : null,
+ /* textColorLink= */ (src.readInt() != 0)
+ ? ColorStateList.CREATOR.createFromParcel(src) : null,
+ /* typeface= */ LeakyTypefaceStorage.readTypefaceFromParcel(src),
+ /* textFontWeight= */ src.readInt(),
+ /* textLocales= */
+ src.readParcelable(LocaleList.class.getClassLoader(), LocaleList.class),
+ /* shadowRadius= */ src.readFloat(),
+ /* shadowDx= */ src.readFloat(),
+ /* shadowDy= */ src.readFloat(),
+ /* shadowColor= */ src.readInt(),
+ /* hasElegantTextHeight= */ src.readBoolean(),
+ /* elegantTextHeight= */ src.readBoolean(),
+ /* hasLetterSpacing= */ src.readBoolean(),
+ /* letterSpacing= */ src.readFloat(),
+ /* fontFeatureSettings= */ src.readString(),
+ /* fontVariationSettings= */ src.readString());
+ }
- mTextFontWeight = src.readInt();
- mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
+ /** @hide */
+ public TextAppearanceSpan(@Nullable String familyName, int style, int textSize,
+ @Nullable ColorStateList textColor, @Nullable ColorStateList textColorLink,
+ @Nullable Typeface typeface,
+ int textFontWeight, @Nullable LocaleList textLocales, float shadowRadius,
+ float shadowDx, float shadowDy, int shadowColor, boolean hasElegantTextHeight,
+ boolean elegantTextHeight, boolean hasLetterSpacing, float letterSpacing,
+ @Nullable String fontFeatureSettings, @Nullable String fontVariationSettings) {
+ mFamilyName = familyName;
+ mStyle = style;
+ mTextSize = textSize;
+ mTextColor = textColor;
+ mTextColorLink = textColorLink;
+ mTypeface = typeface;
- mShadowRadius = src.readFloat();
- mShadowDx = src.readFloat();
- mShadowDy = src.readFloat();
- mShadowColor = src.readInt();
+ mTextFontWeight = textFontWeight;
+ mTextLocales = textLocales;
- mHasElegantTextHeight = src.readBoolean();
- mElegantTextHeight = src.readBoolean();
- mHasLetterSpacing = src.readBoolean();
- mLetterSpacing = src.readFloat();
+ mShadowRadius = shadowRadius;
+ mShadowDx = shadowDx;
+ mShadowDy = shadowDy;
+ mShadowColor = shadowColor;
- mFontFeatureSettings = src.readString();
- mFontVariationSettings = src.readString();
+ mHasElegantTextHeight = hasElegantTextHeight;
+ mElegantTextHeight = elegantTextHeight;
+ mHasLetterSpacing = hasLetterSpacing;
+ mLetterSpacing = letterSpacing;
+
+ mFontFeatureSettings = fontFeatureSettings;
+ mFontVariationSettings = fontVariationSettings;
}
public int getSpanTypeId() {
@@ -564,4 +587,14 @@
+ ", fontVariationSettings='" + getFontVariationSettings() + '\''
+ '}';
}
+
+ /** @hide */
+ public boolean hasElegantTextHeight() {
+ return mHasElegantTextHeight;
+ }
+
+ /** @hide */
+ public boolean hasLetterSpacing() {
+ return mHasLetterSpacing;
+ }
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 12dbc5a..157cec8 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -708,7 +708,7 @@
*/
@Nullable
public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
- Display.Mode[] modes = supportedModes;
+ Display.Mode[] modes = appsSupportedModes;
Display.Mode defaultMode = getDefaultMode();
for (int i = 0; i < modes.length; i++) {
if (modes[i].matches(
@@ -723,7 +723,7 @@
* Returns the list of supported refresh rates in the default mode.
*/
public float[] getDefaultRefreshRates() {
- Display.Mode[] modes = supportedModes;
+ Display.Mode[] modes = appsSupportedModes;
ArraySet<Float> rates = new ArraySet<>();
Display.Mode defaultMode = getDefaultMode();
for (int i = 0; i < modes.length; i++) {
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 6b354a0..0001176 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -100,8 +100,12 @@
if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) {
mTotalScrollPixels %= mTickIntervalPixels;
- // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
- mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+ if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+ mView.performHapticFeedbackForInputDevice(
+ HapticFeedbackConstants.SCROLL_TICK, inputDeviceId, source, /* flags= */ 0);
+ } else {
+ mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+ }
}
}
@@ -115,9 +119,12 @@
if (!mCanPlayLimitFeedback) {
return;
}
-
- // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
- mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+ if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+ mView.performHapticFeedbackForInputDevice(
+ HapticFeedbackConstants.SCROLL_LIMIT, inputDeviceId, source, /* flags= */ 0);
+ } else {
+ mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+ }
mCanPlayLimitFeedback = false;
}
@@ -128,22 +135,28 @@
if (!mHapticScrollFeedbackEnabled) {
return;
}
- // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
- mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+ if (android.os.vibrator.Flags.hapticFeedbackInputSourceCustomizationEnabled()) {
+ mView.performHapticFeedbackForInputDevice(
+ HapticFeedbackConstants.SCROLL_ITEM_FOCUS, inputDeviceId, source,
+ /* flags= */ 0);
+ } else {
+ mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+ }
mCanPlayLimitFeedback = true;
}
private void maybeUpdateCurrentConfig(int deviceId, int source, int axis) {
if (mAxis != axis || mSource != source || mDeviceId != deviceId) {
+ mSource = source;
+ mAxis = axis;
+ mDeviceId = deviceId;
+
if (mDisabledIfViewPlaysScrollHaptics
&& (source == InputDevice.SOURCE_ROTARY_ENCODER)
&& mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) {
mHapticScrollFeedbackEnabled = false;
return;
}
- mSource = source;
- mAxis = axis;
- mDeviceId = deviceId;
mHapticScrollFeedbackEnabled =
mViewConfig.isHapticScrollFeedbackEnabled(deviceId, axis, source);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 762a302..11a3168 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -140,15 +140,6 @@
oneway void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction,
int seqId);
- @UnsupportedAppUsage
- boolean performHapticFeedback(int effectId, int flags, int privFlags);
-
- /**
- * Called by attached views to perform predefined haptic feedback without requiring VIBRATE
- * permission.
- */
- oneway void performHapticFeedbackAsync(int effectId, int flags, int privFlags);
-
/**
* Initiate the drag operation itself
*
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d83f344..7896cbd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -685,9 +685,6 @@
*/
private @InsetsType int mCancelledForNewAnimationTypes;
- private final Runnable mInvokeControllableInsetsChangedListeners =
- this::invokeControllableInsetsChangedListeners;
-
private final InsetsState.OnTraverseCallbacks mRemoveGoneSources =
new InsetsState.OnTraverseCallbacks() {
@@ -2206,7 +2203,6 @@
* @return The types that are now animating due to a listener invoking control/show/hide
*/
private @InsetsType int invokeControllableInsetsChangedListeners() {
- mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners);
mLastStartedAnimTypes = 0;
@InsetsType int types = calculateControllableTypes();
int size = mControllableInsetsChangedListeners.size();
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a7641c0..9e4b27d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -3574,7 +3574,7 @@
checkPreconditions(sc);
if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
- "reparent", this, sc,
+ "setColor", this, sc,
"r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
}
nativeSetColor(mNativeObject, sc.mNativeObject, color);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5f8bea1..dbd65de 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -142,7 +142,6 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.Vibrator;
-import android.os.vibrator.Flags;
import android.service.credentials.CredentialProviderService;
import android.sysprop.DisplayProperties;
import android.text.InputType;
@@ -5712,9 +5711,6 @@
*/
private PointerIcon mMousePointerIcon;
- /** Vibrator for haptic feedback. */
- private Vibrator mVibrator;
-
/**
* @hide
*/
@@ -28667,37 +28663,63 @@
* @param flags Additional flags as per {@link HapticFeedbackConstants}.
*/
public boolean performHapticFeedback(int feedbackConstant, int flags) {
- if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
- || mAttachInfo == null) {
+ if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
return false;
}
+
+ int privFlags = computeHapticFeedbackPrivateFlags();
+ return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
+ }
+
+ /**
+ * <p>Provide haptic feedback to the user for this view.
+ *
+ * <p>Call this method (vs {@link #performHapticFeedback(int)}) to specify more details about
+ * the {@link InputDevice} that caused this haptic feedback. The framework will choose and
+ * provide a haptic feedback based on these details.
+ *
+ * <p>The feedback will only be performed if {@link #isHapticFeedbackEnabled()} is {@code true}.
+ *
+ * @param feedbackConstant One of the constants defined in {@link HapticFeedbackConstants}.
+ * @param inputDeviceId The ID of the {@link InputDevice} that generated the event which
+ * triggered this haptic feedback request.
+ * @param inputSource The input source of the event which triggered this haptic feedback
+ * request, defined as {@code InputDevice#SOURCE_*}.
+ *
+ * @hide
+ */
+ public void performHapticFeedbackForInputDevice(int feedbackConstant, int inputDeviceId,
+ int inputSource, int flags) {
+ if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
+ return;
+ }
+
+ int privFlags = computeHapticFeedbackPrivateFlags();
+ mAttachInfo.mRootCallbacks.performHapticFeedbackForInputDevice(
+ feedbackConstant, inputDeviceId, inputSource, flags, privFlags);
+ }
+
+ private boolean isPerformHapticFeedbackSuppressed(int feedbackConstant, int flags) {
+ if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
+ || mAttachInfo == null
+ || mAttachInfo.mSession == null) {
+ return true;
+ }
//noinspection SimplifiableIfStatement
if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
&& !isHapticFeedbackEnabled()) {
- return false;
+ return true;
}
+ return false;
+ }
+ private int computeHapticFeedbackPrivateFlags() {
int privFlags = 0;
if (mAttachInfo.mViewRootImpl != null
&& mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
}
- if (Flags.useVibratorHapticFeedback()) {
- if (!mAttachInfo.canPerformHapticFeedback()) {
- return false;
- }
- getSystemVibrator().performHapticFeedback(feedbackConstant,
- "View#performHapticFeedback", flags, privFlags);
- return true;
- }
- return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, flags, privFlags);
- }
-
- private Vibrator getSystemVibrator() {
- if (mVibrator != null) {
- return mVibrator;
- }
- return mVibrator = mContext.getSystemService(Vibrator.class);
+ return privFlags;
}
/**
@@ -31731,6 +31753,11 @@
boolean performHapticFeedback(int effectId,
@HapticFeedbackConstants.Flags int flags,
@HapticFeedbackConstants.PrivateFlags int privFlags);
+
+ void performHapticFeedbackForInputDevice(int effectId,
+ int inputDeviceId, int inputSource,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
}
/**
@@ -32297,11 +32324,6 @@
return events;
}
- private boolean canPerformHapticFeedback() {
- return mSession != null
- && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0;
- }
-
@Nullable
ScrollCaptureInternal getScrollCaptureInternal() {
if (mScrollCaptureInternal != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9518abf..1c0700f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -207,6 +207,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.Vibrator;
import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.sysprop.ViewProperties;
@@ -362,14 +363,6 @@
private static final boolean ENABLE_INPUT_LATENCY_TRACKING = true;
/**
- * Controls whether to use the new oneway performHapticFeedback call. This returns
- * true in a few more conditions, but doesn't affect which haptics happen. Notably, it
- * makes the call to performHapticFeedback non-blocking, which reduces potential UI jank.
- * This is intended as a temporary flag, ultimately becoming permanently 'true'.
- */
- private static final boolean USE_ASYNC_PERFORM_HAPTIC_FEEDBACK = true;
-
- /**
* Whether the client (system UI) is handling the transient gesture and the corresponding
* animation.
* @hide
@@ -956,6 +949,11 @@
*/
AudioManager mAudioManager;
+ /**
+ * see {@link #performHapticFeedback(int, int, int)}
+ */
+ Vibrator mVibrator;
+
final AccessibilityManager mAccessibilityManager;
AccessibilityInteractionController mAccessibilityInteractionController;
@@ -2822,6 +2820,12 @@
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.setSurfaceControl(null, null);
}
+
+ // Also reset the VRR relevant values.
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
+ mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
+ mPreferredFrameRate = 0;
+ mLastPreferredFrameRate = 0;
}
/**
@@ -9236,6 +9240,13 @@
return mAudioManager;
}
+ private Vibrator getSystemVibrator() {
+ if (mVibrator == null) {
+ mVibrator = mContext.getSystemService(Vibrator.class);
+ }
+ return mVibrator;
+ }
+
private @Nullable AutofillManager getAutofillManager() {
if (mView instanceof ViewGroup) {
ViewGroup decorView = (ViewGroup) mView;
@@ -9662,17 +9673,23 @@
return false;
}
- try {
- if (USE_ASYNC_PERFORM_HAPTIC_FEEDBACK) {
- mWindowSession.performHapticFeedbackAsync(effectId, flags, privFlags);
- return true;
- } else {
- // Original blocking binder call path.
- return mWindowSession.performHapticFeedback(effectId, flags, privFlags);
- }
- } catch (RemoteException e) {
- return false;
+ getSystemVibrator().performHapticFeedback(
+ effectId, "ViewRootImpl#performHapticFeedback", flags, privFlags);
+ return true;
+ }
+
+ @Override
+ public void performHapticFeedbackForInputDevice(int effectId,
+ int inputDeviceId, int inputSource,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags) {
+ if ((mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+ return;
}
+
+ getSystemVibrator().performHapticFeedbackForInputDevice(effectId,
+ inputDeviceId, inputSource, "ViewRootImpl#performHapticFeedbackForInputDevice",
+ flags, privFlags);
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 85d4ec0..017e004 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -132,6 +132,7 @@
import android.window.TaskFpsCallback;
import android.window.TrustedPresentationThresholds;
+import com.android.internal.R;
import com.android.window.flags.Flags;
import java.lang.annotation.ElementType;
@@ -482,6 +483,11 @@
* @hide
*/
int TRANSIT_PREPARE_BACK_NAVIGATION = 13;
+ /**
+ * An Activity was going to be invisible from back navigation.
+ * @hide
+ */
+ int TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION = 14;
/**
* The first slot for custom transition types. Callers (like Shell) can make use of custom
@@ -512,6 +518,7 @@
TRANSIT_WAKE,
TRANSIT_SLEEP,
TRANSIT_PREPARE_BACK_NAVIGATION,
+ TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION,
TRANSIT_FIRST_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
@@ -1926,6 +1933,7 @@
case TRANSIT_WAKE: return "WAKE";
case TRANSIT_SLEEP: return "SLEEP";
case TRANSIT_PREPARE_BACK_NAVIGATION: return "PREDICTIVE_BACK";
+ case TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION: return "CLOSE_PREDICTIVE_BACK";
case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
default:
if (type > TRANSIT_FIRST_CUSTOM) {
@@ -3468,6 +3476,13 @@
public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25;
/**
+ * Flag to indicate that the window has the
+ * {@link R.styleable.Window_windowOptOutEdgeToEdgeEnforcement} flag set.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE = 1 << 26;
+
+ /**
* Flag to indicate that the window is controlling how it fits window insets on its own.
* So we don't need to adjust its attributes for fitting window insets.
* @hide
@@ -3540,6 +3555,7 @@
PRIVATE_FLAG_NOT_MAGNIFIABLE,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
PRIVATE_FLAG_CONSUME_IME_INSETS,
+ PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
PRIVATE_FLAG_TRUSTED_OVERLAY,
PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME,
@@ -3644,6 +3660,10 @@
equals = PRIVATE_FLAG_CONSUME_IME_INSETS,
name = "CONSUME_IME_INSETS"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
+ equals = PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE,
+ name = "OPTOUT_EDGE_TO_EDGE"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
equals = PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
name = "FIT_INSETS_CONTROLLED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 961a9c4..f50ea91 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -17,6 +17,8 @@
package android.view;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.animation.ValueAnimator;
@@ -26,6 +28,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
+import android.content.res.TypedArray;
import android.graphics.HardwareRenderer;
import android.os.Binder;
import android.os.Build;
@@ -45,6 +48,7 @@
import android.window.InputTransferToken;
import android.window.TrustedPresentationThresholds;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
@@ -356,12 +360,12 @@
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
+ final Context context = view.getContext();
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
- final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
@@ -369,6 +373,14 @@
}
}
+ if (context != null && wparams.type > LAST_APPLICATION_WINDOW) {
+ final TypedArray styles = context.obtainStyledAttributes(R.styleable.Window);
+ if (styles.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) {
+ wparams.privateFlags |= PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
+ }
+ styles.recycle();
+ }
+
ViewRootImpl root;
View panelParentView = null;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 0d027f1..d2747e4 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -504,16 +504,6 @@
}
@Override
- public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
- return false;
- }
-
- @Override
- public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
- performHapticFeedback(effectId, flags, privFlags);
- }
-
- @Override
public android.os.IBinder performDrag(android.view.IWindow window, int flags,
android.view.SurfaceControl surface, int touchSource, int touchDeviceId,
int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 4ab6758..b281015 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2887,6 +2887,11 @@
* initiation delegation was previously requested using
* {@link #prepareStylusHandwritingDelegation(View)} from the delegator.
*
+ * <p>Otherwise, if the delegator view previously started delegation using {@link
+ * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, Executor,
+ * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+ * text from the connectionless session to the delegate view.
+ *
* <p>Note: If delegator and delegate are in different application packages, use
* {@link #acceptStylusHandwritingDelegation(View, String)} instead.</p>
*
@@ -2895,14 +2900,9 @@
* {@link #prepareStylusHandwritingDelegation(View)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View)
* @see #acceptStylusHandwritingDelegation(View, String)
+ * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, Executor,
+ * ConnectionlessHandwritingCallback)
*/
- // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
- // <p>Otherwise, if the delegator view previously started delegation using {@link
- // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo)},
- // requests the IME to commit the recognised handwritten text from the connectionless session to
- // the delegate view.
- // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
- // CursorAnchorInfo)
public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
return startStylusHandwritingInternal(
delegateView, delegateView.getContext().getOpPackageName(),
@@ -2915,6 +2915,11 @@
* {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
* belongs to a specified delegate package.
*
+ * <p>Otherwise, if the delegator view previously started delegation using {@link
+ * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+ * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+ * text from the connectionless session to the delegate view.
+ *
* <p>Note: If delegator and delegate are in the same application package, use
* {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
*
@@ -2924,15 +2929,10 @@
* #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
+ * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String,
+ * Executor, ConnectionlessHandwritingCallback)
* TODO (b/293640003): deprecate this method once flag is enabled.
*/
- // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
- // <p>Otherwise, if the delegator view previously started delegation using {@link
- // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
- // String)}, requests the IME to commit the recognised handwritten text from the connectionless
- // session to the delegate view.
- // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
- // CursorAnchorInfo, String)
public boolean acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName) {
Objects.requireNonNull(delegatorPackageName);
@@ -2946,6 +2946,11 @@
* {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
* belongs to a specified delegate package.
*
+ * <p>Otherwise, if the delegator view previously started delegation using {@link
+ * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+ * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+ * text from the connectionless session to the delegate view.
+ *
* @param delegateView delegate view capable of receiving input via {@link InputConnection}
* on which {@link #startStylusHandwriting(View)} will be called.
* @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
@@ -2957,6 +2962,8 @@
* The framework only holds a weak reference.
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
+ * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String,
+ * Executor, ConnectionlessHandwritingCallback)
*/
@FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY)
public void acceptStylusHandwritingDelegation(
@@ -2977,6 +2984,11 @@
* #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to
* a specified delegate package.
*
+ * <p>Otherwise, if the delegator view previously started delegation using {@link
+ * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
+ * ConnectionlessHandwritingCallback)}, requests the IME to commit the recognised handwritten
+ * text from the connectionless session to the delegate view.
+ *
* <p>Note: If delegator and delegate are in the same application package, use {@link
* #acceptStylusHandwritingDelegation(View)} instead.
*
@@ -2988,15 +3000,9 @@
* hold a reference to the callback. The framework only holds a weak reference.
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
+ * @see #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String,
+ * Executor, ConnectionlessHandwritingCallback)
*/
- // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
- // <p>Otherwise, if the delegator view previously started delegation using {@link
- // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
- // String)}, requests the IME to commit the recognised handwritten text from the connectionless
- // session to the delegate view.
- // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
- // CursorAnchorInfo, String)
- //
@FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
public void acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName,
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 0b67cad..9931aea 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -328,7 +328,7 @@
if (running) {
updateText(SystemClock.elapsedRealtime());
dispatchChronometerTick();
- postDelayed(mTickRunnable, 1000);
+ postTickOnNextSecond();
} else {
removeCallbacks(mTickRunnable);
}
@@ -342,11 +342,17 @@
if (mRunning) {
updateText(SystemClock.elapsedRealtime());
dispatchChronometerTick();
- postDelayed(mTickRunnable, 1000);
+ postTickOnNextSecond();
}
}
};
+ private void postTickOnNextSecond() {
+ long nowMillis = SystemClock.elapsedRealtime();
+ int millis = (int) ((nowMillis - mBase) % 1000);
+ postDelayed(mTickRunnable, 1000 - millis);
+ }
+
void dispatchChronometerTick() {
if (mOnChronometerTickListener != null) {
mOnChronometerTickListener.onChronometerTick(this);
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 63f8ee7..ed6ec32 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -267,7 +267,7 @@
* @param buttonView The compound button view whose state has changed.
* @param isChecked The new checked state of buttonView.
*/
- void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
+ void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked);
}
/**
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index d445fdc..70fe6d5 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -366,7 +366,7 @@
* @param group the group in which the checked radio button has changed
* @param checkedId the unique identifier of the newly checked radio button
*/
- public void onCheckedChanged(RadioGroup group, @IdRes int checkedId);
+ void onCheckedChanged(@NonNull RadioGroup group, @IdRes int checkedId);
}
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
diff --git a/core/java/android/widget/RemoteViewsSerializers.java b/core/java/android/widget/RemoteViewsSerializers.java
index 600fea4..080f22e 100644
--- a/core/java/android/widget/RemoteViewsSerializers.java
+++ b/core/java/android/widget/RemoteViewsSerializers.java
@@ -15,12 +15,55 @@
*/
package android.widget;
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.noBreakNoHyphenationSpan;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.FlaggedApi;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlendMode;
import android.graphics.drawable.Icon;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
+import android.os.PersistableBundle;
+import android.text.Annotation;
+import android.text.Layout;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AccessibilityClickableSpan;
+import android.text.style.AccessibilityReplacementSpan;
+import android.text.style.AccessibilityURLSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.EasyEditSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LeadingMarginSpan;
+import android.text.style.LineBackgroundSpan;
+import android.text.style.LineBreakConfigSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.LocaleSpan;
+import android.text.style.QuoteSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.SpellCheckSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuggestionRangeSpan;
+import android.text.style.SuggestionSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TtsSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.proto.ProtoInputStream;
@@ -29,7 +72,11 @@
import androidx.annotation.NonNull;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Function;
/**
@@ -59,12 +106,13 @@
break;
case Icon.TYPE_ADAPTIVE_BITMAP:
final ByteArrayOutputStream adaptiveBitmapBytes = new ByteArrayOutputStream();
- icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100,
- adaptiveBitmapBytes);
+ icon.getBitmap()
+ .compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, adaptiveBitmapBytes);
out.write(RemoteViewsProto.Icon.ADAPTIVE_BITMAP, adaptiveBitmapBytes.toByteArray());
break;
case Icon.TYPE_RESOURCE:
- out.write(RemoteViewsProto.Icon.RESOURCE,
+ out.write(
+ RemoteViewsProto.Icon.RESOURCE,
appResources.getResourceName(icon.getResId()));
break;
case Icon.TYPE_DATA:
@@ -91,7 +139,8 @@
while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (in.getFieldNumber()) {
case (int) RemoteViewsProto.Icon.BLEND_MODE:
- values.put(RemoteViewsProto.Icon.BLEND_MODE,
+ values.put(
+ RemoteViewsProto.Icon.BLEND_MODE,
in.readInt(RemoteViewsProto.Icon.BLEND_MODE));
break;
case (int) RemoteViewsProto.Icon.TINT_LIST:
@@ -101,7 +150,8 @@
break;
case (int) RemoteViewsProto.Icon.BITMAP:
byte[] bitmapData = in.readBytes(RemoteViewsProto.Icon.BITMAP);
- values.put(RemoteViewsProto.Icon.BITMAP,
+ values.put(
+ RemoteViewsProto.Icon.BITMAP,
BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length));
break;
case (int) RemoteViewsProto.Icon.ADAPTIVE_BITMAP:
@@ -112,23 +162,27 @@
bitmapAdaptiveData.length));
break;
case (int) RemoteViewsProto.Icon.RESOURCE:
- values.put(RemoteViewsProto.Icon.RESOURCE,
+ values.put(
+ RemoteViewsProto.Icon.RESOURCE,
in.readString(RemoteViewsProto.Icon.RESOURCE));
break;
case (int) RemoteViewsProto.Icon.DATA:
- values.put(RemoteViewsProto.Icon.DATA,
- in.readBytes(RemoteViewsProto.Icon.DATA));
+ values.put(
+ RemoteViewsProto.Icon.DATA, in.readBytes(RemoteViewsProto.Icon.DATA));
break;
case (int) RemoteViewsProto.Icon.URI:
values.put(RemoteViewsProto.Icon.URI, in.readString(RemoteViewsProto.Icon.URI));
break;
case (int) RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP:
- values.put(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
+ values.put(
+ RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
in.readString(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP));
break;
default:
- Log.w(TAG, "Unhandled field while reading Icon proto!\n"
- + ProtoUtils.currentFieldToString(in));
+ Log.w(
+ TAG,
+ "Unhandled field while reading Icon proto!\n"
+ + ProtoUtils.currentFieldToString(in));
}
}
@@ -174,4 +228,1279 @@
return icon;
};
}
+
+ public static void writeCharSequenceToProto(@NonNull ProtoOutputStream out,
+ @NonNull CharSequence cs) {
+ out.write(RemoteViewsProto.CharSequence.TEXT, cs.toString());
+ if (!(cs instanceof Spanned sp)) return;
+
+ Object[] os = sp.getSpans(0, cs.length(), Object.class);
+ for (Object original : os) {
+ Object prop = original;
+ if (prop instanceof CharacterStyle) {
+ prop = ((CharacterStyle) prop).getUnderlying();
+ }
+
+ final long spansToken = out.start(RemoteViewsProto.CharSequence.SPANS);
+ out.write(RemoteViewsProto.CharSequence.Span.START, sp.getSpanStart(original));
+ out.write(RemoteViewsProto.CharSequence.Span.END, sp.getSpanEnd(original));
+ out.write(RemoteViewsProto.CharSequence.Span.FLAGS, sp.getSpanFlags(original));
+
+ if (prop instanceof AbsoluteSizeSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE);
+ writeAbsoluteSizeSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof AccessibilityClickableSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE);
+ writeAccessibilityClickableSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof AccessibilityReplacementSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT);
+ writeAccessibilityReplacementSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof AccessibilityURLSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL);
+ writeAccessibilityURLSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof Annotation span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ANNOTATION);
+ writeAnnotationToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof BackgroundColorSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR);
+ writeBackgroundColorSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof BulletSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.BULLET);
+ writeBulletSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof EasyEditSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.EASY_EDIT);
+ writeEasyEditSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof ForegroundColorSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR);
+ writeForegroundColorSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (noBreakNoHyphenationSpan() && prop instanceof LineBreakConfigSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LINE_BREAK);
+ writeLineBreakConfigSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof LocaleSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LOCALE);
+ writeLocaleSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof QuoteSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.QUOTE);
+ writeQuoteSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof RelativeSizeSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE);
+ writeRelativeSizeSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof ScaleXSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SCALE_X);
+ writeScaleXSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof SpellCheckSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SPELL_CHECK);
+ writeSpellCheckSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof LineBackgroundSpan.Standard span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND);
+ writeLineBackgroundSpanStandardToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof LineHeightSpan.Standard span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LINE_HEIGHT);
+ writeLineHeightSpanStandardToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof LeadingMarginSpan.Standard span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.LEADING_MARGIN);
+ writeLeadingMarginSpanStandardToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof AlignmentSpan.Standard span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.ALIGNMENT);
+ writeAlignmentSpanStandardToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof StrikethroughSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.STRIKETHROUGH);
+ writeStrikethroughSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof StyleSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.STYLE);
+ writeStyleSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof SubscriptSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUBSCRIPT);
+ writeSubscriptSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof SuggestionRangeSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE);
+ writeSuggestionRangeSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof SuggestionSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUGGESTION);
+ writeSuggestionSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof SuperscriptSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.SUPERSCRIPT);
+ writeSuperscriptSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof TextAppearanceSpan span) {
+ final long spanToken = out.start(
+ RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE);
+ writeTextAppearanceSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof TtsSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.TTS);
+ writeTtsSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof TypefaceSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.TYPEFACE);
+ writeTypefaceSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof URLSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.URL);
+ writeURLSpanToProto(out, span);
+ out.end(spanToken);
+ } else if (prop instanceof UnderlineSpan span) {
+ final long spanToken = out.start(RemoteViewsProto.CharSequence.Span.UNDERLINE);
+ writeUnderlineSpanToProto(out, span);
+ out.end(spanToken);
+ }
+ out.end(spansToken);
+ }
+ }
+
+ public static CharSequence createCharSequenceFromProto(ProtoInputStream in) throws Exception {
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ boolean hasSpans = false;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.TEXT:
+ String text = in.readString(RemoteViewsProto.CharSequence.TEXT);
+ builder.append(text);
+ break;
+ case (int) RemoteViewsProto.CharSequence.SPANS:
+ hasSpans = true;
+ final long spansToken = in.start(RemoteViewsProto.CharSequence.SPANS);
+ createSpanFromProto(in, builder);
+ in.end(spansToken);
+ break;
+ default:
+ Log.w(TAG, "Unhandled field while reading CharSequence proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return hasSpans ? builder : builder.toString();
+ }
+
+ private static void createSpanFromProto(ProtoInputStream in, SpannableStringBuilder builder)
+ throws Exception {
+ int start = 0;
+ int end = 0;
+ int flags = 0;
+ Object what = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.START:
+ start = in.readInt(RemoteViewsProto.CharSequence.Span.START);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.END:
+ end = in.readInt(RemoteViewsProto.CharSequence.Span.END);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.FLAGS:
+ flags = in.readInt(RemoteViewsProto.CharSequence.Span.FLAGS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE:
+ final long asToken = in.start(RemoteViewsProto.CharSequence.Span.ABSOLUTE_SIZE);
+ what = createAbsoluteSizeSpanFromProto(in);
+ in.end(asToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE:
+ final long acToken = in.start(
+ RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_CLICKABLE);
+ what = createAccessibilityClickableSpanFromProto(in);
+ in.end(acToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT:
+ final long arToken = in.start(
+ RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_REPLACEMENT);
+ what = createAccessibilityReplacementSpanFromProto(in);
+ in.end(arToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL:
+ final long auToken = in.start(
+ RemoteViewsProto.CharSequence.Span.ACCESSIBILITY_URL);
+ what = createAccessibilityURLSpanFromProto(in);
+ in.end(auToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.ALIGNMENT:
+ final long aToken = in.start(RemoteViewsProto.CharSequence.Span.ALIGNMENT);
+ what = createAlignmentSpanStandardFromProto(in);
+ in.end(aToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.ANNOTATION:
+ final long annToken = in.start(RemoteViewsProto.CharSequence.Span.ANNOTATION);
+ what = createAnnotationFromProto(in);
+ in.end(annToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR:
+ final long bcToken = in.start(
+ RemoteViewsProto.CharSequence.Span.BACKGROUND_COLOR);
+ what = createBackgroundColorSpanFromProto(in);
+ in.end(bcToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.BULLET:
+ final long bToken = in.start(RemoteViewsProto.CharSequence.Span.BULLET);
+ what = createBulletSpanFromProto(in);
+ in.end(bToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.EASY_EDIT:
+ final long eeToken = in.start(RemoteViewsProto.CharSequence.Span.EASY_EDIT);
+ what = createEasyEditSpanFromProto(in);
+ in.end(eeToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR:
+ final long fcToken = in.start(
+ RemoteViewsProto.CharSequence.Span.FOREGROUND_COLOR);
+ what = createForegroundColorSpanFromProto(in);
+ in.end(fcToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LEADING_MARGIN:
+ final long lmToken = in.start(
+ RemoteViewsProto.CharSequence.Span.LEADING_MARGIN);
+ what = createLeadingMarginSpanStandardFromProto(in);
+ in.end(lmToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND:
+ final long lbToken = in.start(
+ RemoteViewsProto.CharSequence.Span.LINE_BACKGROUND);
+ what = createLineBackgroundSpanStandardFromProto(in);
+ in.end(lbToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LINE_BREAK:
+ if (!noBreakNoHyphenationSpan()) {
+ continue;
+ }
+ final long lbrToken = in.start(RemoteViewsProto.CharSequence.Span.LINE_BREAK);
+ what = createLineBreakConfigSpanFromProto(in);
+ in.end(lbrToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LINE_HEIGHT:
+ final long lhToken = in.start(RemoteViewsProto.CharSequence.Span.LINE_HEIGHT);
+ what = createLineHeightSpanStandardFromProto(in);
+ in.end(lhToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LOCALE:
+ final long lToken = in.start(RemoteViewsProto.CharSequence.Span.LOCALE);
+ what = createLocaleSpanFromProto(in);
+ in.end(lToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.QUOTE:
+ final long qToken = in.start(RemoteViewsProto.CharSequence.Span.QUOTE);
+ what = createQuoteSpanFromProto(in);
+ in.end(qToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE:
+ final long rsToken = in.start(RemoteViewsProto.CharSequence.Span.RELATIVE_SIZE);
+ what = createRelativeSizeSpanFromProto(in);
+ in.end(rsToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.SCALE_X:
+ final long sxToken = in.start(RemoteViewsProto.CharSequence.Span.SCALE_X);
+ what = createScaleXSpanFromProto(in);
+ in.end(sxToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.SPELL_CHECK:
+ final long scToken = in.start(RemoteViewsProto.CharSequence.Span.SPELL_CHECK);
+ what = createSpellCheckSpanFromProto(in);
+ in.end(scToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.STRIKETHROUGH:
+ final long stToken = in.start(RemoteViewsProto.CharSequence.Span.STRIKETHROUGH);
+ what = createStrikethroughSpanFromProto(in);
+ in.end(stToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.STYLE:
+ final long sToken = in.start(RemoteViewsProto.CharSequence.Span.STYLE);
+ what = createStyleSpanFromProto(in);
+ in.end(sToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.SUBSCRIPT:
+ final long suToken = in.start(RemoteViewsProto.CharSequence.Span.SUBSCRIPT);
+ what = createSubscriptSpanFromProto(in);
+ in.end(suToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE:
+ final long srToken = in.start(
+ RemoteViewsProto.CharSequence.Span.SUGGESTION_RANGE);
+ what = createSuggestionRangeSpanFromProto(in);
+ in.end(srToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.SUGGESTION:
+ final long sugToken = in.start(RemoteViewsProto.CharSequence.Span.SUGGESTION);
+ what = createSuggestionSpanFromProto(in);
+ in.end(sugToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.SUPERSCRIPT:
+ final long supToken = in.start(RemoteViewsProto.CharSequence.Span.SUPERSCRIPT);
+ what = createSuperscriptSpanFromProto(in);
+ in.end(supToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE:
+ final long taToken = in.start(
+ RemoteViewsProto.CharSequence.Span.TEXT_APPEARANCE);
+ what = createTextAppearanceSpanFromProto(in);
+ in.end(taToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TTS:
+ final long ttsToken = in.start(RemoteViewsProto.CharSequence.Span.TTS);
+ what = createTtsSpanFromProto(in);
+ in.end(ttsToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TYPEFACE:
+ final long tfToken = in.start(RemoteViewsProto.CharSequence.Span.TYPEFACE);
+ what = createTypefaceSpanFromProto(in);
+ in.end(tfToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.UNDERLINE:
+ final long unToken = in.start(RemoteViewsProto.CharSequence.Span.UNDERLINE);
+ what = createUnderlineSpanFromProto(in);
+ in.end(unToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.URL:
+ final long urlToken = in.start(RemoteViewsProto.CharSequence.Span.URL);
+ what = createURLSpanFromProto(in);
+ in.end(urlToken);
+ break;
+ default:
+ Log.w(TAG, "Unhandled field while reading CharSequence proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ if (what == null) {
+ return;
+ }
+ builder.setSpan(what, start, end, flags);
+ }
+
+ public static AbsoluteSizeSpan createAbsoluteSizeSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ int size = 0;
+ boolean dip = false;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE:
+ size = in.readInt(RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP:
+ dip = in.readBoolean(RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP);
+ break;
+ default:
+ Log.w("AbsoluteSizeSpan",
+ "Unhandled field while reading AbsoluteSizeSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new AbsoluteSizeSpan(size, dip);
+ }
+
+ public static void writeAbsoluteSizeSpanToProto(@NonNull ProtoOutputStream out,
+ AbsoluteSizeSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.AbsoluteSize.SIZE, span.getSize());
+ out.write(RemoteViewsProto.CharSequence.Span.AbsoluteSize.DIP, span.getDip());
+ }
+
+ public static AccessibilityClickableSpan createAccessibilityClickableSpanFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int originalClickableSpanId = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span
+ .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID:
+ originalClickableSpanId = in.readInt(
+ RemoteViewsProto.CharSequence.Span
+ .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID);
+ break;
+ default:
+ Log.w("AccessibilityClickable",
+ "Unhandled field while reading" + " AccessibilityClickableSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new AccessibilityClickableSpan(originalClickableSpanId);
+ }
+
+ public static void writeAccessibilityClickableSpanToProto(@NonNull ProtoOutputStream out,
+ AccessibilityClickableSpan span) {
+ out.write(
+ RemoteViewsProto.CharSequence.Span
+ .AccessibilityClickable.ORIGINAL_CLICKABLE_SPAN_ID,
+ span.getOriginalClickableSpanId());
+ }
+
+ public static AccessibilityReplacementSpan createAccessibilityReplacementSpanFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ CharSequence contentDescription = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span
+ .AccessibilityReplacement.CONTENT_DESCRIPTION:
+ final long token = in.start(
+ RemoteViewsProto.CharSequence.Span
+ .AccessibilityReplacement.CONTENT_DESCRIPTION);
+ contentDescription = createCharSequenceFromProto(in);
+ in.end(token);
+ break;
+ default:
+ Log.w("AccessibilityReplacemen", "Unhandled field while reading"
+ + " AccessibilityReplacementSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new AccessibilityReplacementSpan(contentDescription);
+ }
+
+ public static void writeAccessibilityReplacementSpanToProto(@NonNull ProtoOutputStream out,
+ AccessibilityReplacementSpan span) {
+ final long token = out.start(
+ RemoteViewsProto.CharSequence.Span.AccessibilityReplacement.CONTENT_DESCRIPTION);
+ CharSequence description = span.getContentDescription();
+ if (description != null) {
+ writeCharSequenceToProto(out, description);
+ }
+ out.end(token);
+ }
+
+ public static AccessibilityURLSpan createAccessibilityURLSpanFromProto(ProtoInputStream in)
+ throws Exception {
+ String url = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL:
+ url = in.readString(RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL);
+ break;
+ default:
+ Log.w("AccessibilityURLSpan",
+ "Unhandled field while reading AccessibilityURLSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new AccessibilityURLSpan(new URLSpan(url));
+ }
+
+ public static void writeAccessibilityURLSpanToProto(@NonNull ProtoOutputStream out,
+ AccessibilityURLSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.AccessibilityUrl.URL, span.getURL());
+ }
+
+ public static AlignmentSpan.Standard createAlignmentSpanStandardFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ String alignment = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT:
+ alignment = in.readString(
+ RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT);
+ break;
+ default:
+ Log.w("AlignmentSpan",
+ "Unhandled field while reading AlignmentSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new AlignmentSpan.Standard(Layout.Alignment.valueOf(alignment));
+ }
+
+ public static void writeAlignmentSpanStandardToProto(@NonNull ProtoOutputStream out,
+ AlignmentSpan.Standard span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Alignment.ALIGNMENT,
+ span.getAlignment().name());
+ }
+
+ public static Annotation createAnnotationFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ String key = null;
+ String value = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Annotation.KEY:
+ key = in.readString(RemoteViewsProto.CharSequence.Span.Annotation.KEY);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Annotation.VALUE:
+ value = in.readString(RemoteViewsProto.CharSequence.Span.Annotation.VALUE);
+ break;
+ default:
+ Log.w("Annotation", "Unhandled field while reading" + " Annotation proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new Annotation(key, value);
+ }
+
+ public static void writeAnnotationToProto(@NonNull ProtoOutputStream out, Annotation span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Annotation.KEY, span.getKey());
+ out.write(RemoteViewsProto.CharSequence.Span.Annotation.VALUE, span.getValue());
+ }
+
+ public static BackgroundColorSpan createBackgroundColorSpanFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int color = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR:
+ color = in.readInt(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR);
+ break;
+ default:
+ Log.w("BackgroundColorSpan",
+ "Unhandled field while reading" + " BackgroundColorSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new BackgroundColorSpan(color);
+ }
+
+ public static void writeBackgroundColorSpanToProto(@NonNull ProtoOutputStream out,
+ BackgroundColorSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR,
+ span.getBackgroundColor());
+ }
+
+ public static BulletSpan createBulletSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ int bulletRadius = 0;
+ int color = 0;
+ int gapWidth = 0;
+ boolean wantColor = false;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS:
+ bulletRadius = in.readInt(
+ RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Bullet.COLOR:
+ color = in.readInt(RemoteViewsProto.CharSequence.Span.Bullet.COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH:
+ gapWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR:
+ wantColor = in.readBoolean(
+ RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR);
+ break;
+ default:
+ Log.w("BulletSpan", "Unhandled field while reading BulletSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new BulletSpan(gapWidth, color, wantColor, bulletRadius);
+ }
+
+ public static void writeBulletSpanToProto(@NonNull ProtoOutputStream out, BulletSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Bullet.BULLET_RADIUS, span.getBulletRadius());
+ out.write(RemoteViewsProto.CharSequence.Span.Bullet.COLOR, span.getColor());
+ out.write(RemoteViewsProto.CharSequence.Span.Bullet.GAP_WIDTH, span.getGapWidth());
+ out.write(RemoteViewsProto.CharSequence.Span.Bullet.WANT_COLOR, span.getWantColor());
+ }
+
+ public static EasyEditSpan createEasyEditSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ return new EasyEditSpan();
+ }
+
+ public static void writeEasyEditSpanToProto(@NonNull ProtoOutputStream out, EasyEditSpan span) {
+ }
+
+ public static ForegroundColorSpan createForegroundColorSpanFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int color = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR:
+ color = in.readInt(RemoteViewsProto.CharSequence.Span.BackgroundColor.COLOR);
+ break;
+ default:
+ Log.w("ForegroundColorSpan",
+ "Unhandled field while reading" + " ForegroundColorSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new ForegroundColorSpan(color);
+ }
+
+ public static LeadingMarginSpan.Standard createLeadingMarginSpanStandardFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int first = 0;
+ int rest = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST:
+ first = in.readInt(RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LeadingMargin.REST:
+ rest = in.readInt(RemoteViewsProto.CharSequence.Span.LeadingMargin.REST);
+ break;
+ default:
+ Log.w("LeadingMarginSpan",
+ "Unhandled field while reading LeadingMarginSpan" + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new LeadingMarginSpan.Standard(first, rest);
+ }
+
+ public static void writeLeadingMarginSpanStandardToProto(@NonNull ProtoOutputStream out,
+ LeadingMarginSpan.Standard span) {
+ out.write(RemoteViewsProto.CharSequence.Span.LeadingMargin.FIRST,
+ span.getLeadingMargin(/* first= */ true));
+ out.write(RemoteViewsProto.CharSequence.Span.LeadingMargin.REST,
+ span.getLeadingMargin(/* first= */ false));
+ }
+
+ public static void writeForegroundColorSpanToProto(@NonNull ProtoOutputStream out,
+ ForegroundColorSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.ForegroundColor.COLOR,
+ span.getForegroundColor());
+ }
+
+ public static LineBackgroundSpan.Standard createLineBackgroundSpanStandardFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int color = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.LineBackground.COLOR:
+ color = in.readInt(RemoteViewsProto.CharSequence.Span.LineBackground.COLOR);
+ break;
+ default:
+ Log.w("LineBackgroundSpan",
+ "Unhandled field while reading" + " LineBackgroundSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new LineBackgroundSpan.Standard(color);
+ }
+
+ public static void writeLineBackgroundSpanStandardToProto(@NonNull ProtoOutputStream out,
+ LineBackgroundSpan.Standard span) {
+ out.write(RemoteViewsProto.CharSequence.Span.LineBackground.COLOR, span.getColor());
+ }
+
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static LineBreakConfigSpan createLineBreakConfigSpanFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int lineBreakStyle = 0;
+ int lineBreakWordStyle = 0;
+ int hyphenation = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE:
+ lineBreakStyle = in.readInt(
+ RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE:
+ lineBreakWordStyle = in.readInt(
+ RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION:
+ hyphenation = in.readInt(
+ RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION);
+ break;
+ default:
+ Log.w("LineBreakConfigSpan",
+ "Unhandled field while reading " + "LineBreakConfigSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ LineBreakConfig lbc = new LineBreakConfig.Builder().setLineBreakStyle(
+ lineBreakStyle).setLineBreakWordStyle(lineBreakWordStyle).setHyphenation(
+ hyphenation).build();
+ return new LineBreakConfigSpan(lbc);
+ }
+
+ @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+ public static void writeLineBreakConfigSpanToProto(@NonNull ProtoOutputStream out,
+ LineBreakConfigSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_STYLE,
+ span.getLineBreakConfig().getLineBreakStyle());
+ out.write(RemoteViewsProto.CharSequence.Span.LineBreak.LINE_BREAK_WORD_STYLE,
+ span.getLineBreakConfig().getLineBreakWordStyle());
+ out.write(RemoteViewsProto.CharSequence.Span.LineBreak.HYPHENATION,
+ span.getLineBreakConfig().getHyphenation());
+ }
+
+ public static LineHeightSpan.Standard createLineHeightSpanStandardFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int height = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT:
+ height = in.readInt(RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT);
+ break;
+ default:
+ Log.w("LineHeightSpan.Standard",
+ "Unhandled field while reading" + " LineHeightSpan.Standard proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new LineHeightSpan.Standard(height);
+ }
+
+ public static void writeLineHeightSpanStandardToProto(@NonNull ProtoOutputStream out,
+ LineHeightSpan.Standard span) {
+ out.write(RemoteViewsProto.CharSequence.Span.LineHeight.HEIGHT, span.getHeight());
+ }
+
+ public static LocaleSpan createLocaleSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ String languageTags = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS:
+ languageTags = in.readString(
+ RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS);
+ break;
+ default:
+ Log.w("LocaleSpan", "Unhandled field while reading" + " LocaleSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new LocaleSpan(LocaleList.forLanguageTags(languageTags));
+ }
+
+ public static void writeLocaleSpanToProto(@NonNull ProtoOutputStream out, LocaleSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Locale.LANGUAGE_TAGS,
+ span.getLocales().toLanguageTags());
+ }
+
+ public static QuoteSpan createQuoteSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ int color = 0;
+ int stripeWidth = 0;
+ int gapWidth = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Quote.COLOR:
+ color = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH:
+ stripeWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH:
+ gapWidth = in.readInt(RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH);
+ break;
+ default:
+ Log.w("QuoteSpan", "Unhandled field while reading QuoteSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new QuoteSpan(color, stripeWidth, gapWidth);
+ }
+
+ public static void writeQuoteSpanToProto(@NonNull ProtoOutputStream out, QuoteSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Quote.COLOR, span.getColor());
+ out.write(RemoteViewsProto.CharSequence.Span.Quote.STRIPE_WIDTH, span.getStripeWidth());
+ out.write(RemoteViewsProto.CharSequence.Span.Quote.GAP_WIDTH, span.getGapWidth());
+ }
+
+ public static RelativeSizeSpan createRelativeSizeSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ float proportion = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION:
+ proportion = in.readFloat(
+ RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION);
+ break;
+ default:
+ Log.w("RelativeSizeSpan",
+ "Unhandled field while reading" + " RelativeSizeSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new RelativeSizeSpan(proportion);
+ }
+
+ public static void writeRelativeSizeSpanToProto(@NonNull ProtoOutputStream out,
+ RelativeSizeSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.RelativeSize.PROPORTION, span.getSizeChange());
+ }
+
+ public static ScaleXSpan createScaleXSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ float proportion = 0f;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION:
+ proportion = in.readFloat(RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION);
+ break;
+ default:
+ Log.w("ScaleXSpan", "Unhandled field while reading" + " ScaleXSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new ScaleXSpan(proportion);
+ }
+
+ public static void writeScaleXSpanToProto(@NonNull ProtoOutputStream out, ScaleXSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.ScaleX.PROPORTION, span.getScaleX());
+ }
+
+ public static SpellCheckSpan createSpellCheckSpanFromProto(@NonNull ProtoInputStream in) {
+ return new SpellCheckSpan();
+ }
+
+ public static void writeSpellCheckSpanToProto(@NonNull ProtoOutputStream out,
+ SpellCheckSpan span) {
+ }
+
+ public static StrikethroughSpan createStrikethroughSpanFromProto(@NonNull ProtoInputStream in) {
+ return new StrikethroughSpan();
+ }
+
+ public static void writeStrikethroughSpanToProto(@NonNull ProtoOutputStream out,
+ StrikethroughSpan span) {
+ }
+
+ public static StyleSpan createStyleSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ int style = 0;
+ int fontWeightAdjustment = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Style.STYLE:
+ style = in.readInt(RemoteViewsProto.CharSequence.Span.Style.STYLE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT:
+ fontWeightAdjustment = in.readInt(
+ RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT);
+ break;
+ default:
+ Log.w("StyleSpan", "Unhandled field while reading StyleSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new StyleSpan(style, fontWeightAdjustment);
+ }
+
+ public static void writeStyleSpanToProto(@NonNull ProtoOutputStream out, StyleSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Style.STYLE, span.getStyle());
+ out.write(RemoteViewsProto.CharSequence.Span.Style.FONT_WEIGHT_ADJUSTMENT,
+ span.getFontWeightAdjustment());
+ }
+
+ public static SubscriptSpan createSubscriptSpanFromProto(@NonNull ProtoInputStream in) {
+ return new SubscriptSpan();
+ }
+
+ public static void writeSubscriptSpanToProto(@NonNull ProtoOutputStream out,
+ SubscriptSpan span) {
+ }
+
+ public static SuggestionRangeSpan createSuggestionRangeSpanFromProto(
+ @NonNull ProtoInputStream in) throws Exception {
+ int backgroundColor = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR:
+ backgroundColor = in.readInt(
+ RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR);
+ break;
+ default:
+ Log.w("SuggestionRangeSpan",
+ "Unhandled field while reading" + " SuggestionRangeSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ SuggestionRangeSpan span = new SuggestionRangeSpan();
+ span.setBackgroundColor(backgroundColor);
+ return span;
+ }
+
+ public static void writeSuggestionRangeSpanToProto(@NonNull ProtoOutputStream out,
+ SuggestionRangeSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.SuggestionRange.BACKGROUND_COLOR,
+ span.getBackgroundColor());
+ }
+
+ public static SuggestionSpan createSuggestionSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ List<String> suggestions = new ArrayList<>();
+ int flags = 0;
+ String localeStringForCompatibility = null;
+ String languageTag = null;
+ int hashCode = 0;
+ int easyCorrectUnderlineColor = 0;
+ float easyCorrectUnderlineThickness = 0;
+ int misspelledUnderlineColor = 0;
+ float misspelledUnderlineThickness = 0;
+ int autoCorrectionUnderlineColor = 0;
+ float autoCorrectionUnderlineThickness = 0;
+ int grammarErrorUnderlineColor = 0;
+ float grammarErrorUnderlineThickness = 0;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS:
+ suggestions.add(in.readString(
+ RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS));
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS:
+ flags = in.readInt(RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.LOCALE_STRING_FOR_COMPATIBILITY:
+ localeStringForCompatibility = in.readString(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.LOCALE_STRING_FOR_COMPATIBILITY);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG:
+ languageTag = in.readString(
+ RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE:
+ hashCode = in.readInt(RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.EASY_CORRECT_UNDERLINE_COLOR:
+ easyCorrectUnderlineColor = in.readInt(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.EASY_CORRECT_UNDERLINE_COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS:
+ easyCorrectUnderlineThickness = in.readFloat(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.MISSPELLED_UNDERLINE_COLOR:
+ misspelledUnderlineColor = in.readInt(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.MISSPELLED_UNDERLINE_COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.MISSPELLED_UNDERLINE_THICKNESS:
+ misspelledUnderlineThickness = in.readFloat(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.MISSPELLED_UNDERLINE_THICKNESS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR:
+ autoCorrectionUnderlineColor = in.readInt(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS:
+ autoCorrectionUnderlineThickness = in.readFloat(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR:
+ grammarErrorUnderlineColor = in.readInt(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS:
+ grammarErrorUnderlineThickness = in.readFloat(
+ RemoteViewsProto.CharSequence.Span
+ .Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS);
+ break;
+ default:
+ Log.w("SuggestionSpan",
+ "Unhandled field while reading SuggestionSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ String[] suggestionsArray = new String[suggestions.size()];
+ suggestions.toArray(suggestionsArray);
+ return new SuggestionSpan(suggestionsArray, flags, localeStringForCompatibility,
+ languageTag, hashCode, easyCorrectUnderlineColor, easyCorrectUnderlineThickness,
+ misspelledUnderlineColor, misspelledUnderlineThickness,
+ autoCorrectionUnderlineColor, autoCorrectionUnderlineThickness,
+ grammarErrorUnderlineColor, grammarErrorUnderlineThickness);
+ }
+
+ public static void writeSuggestionSpanToProto(@NonNull ProtoOutputStream out,
+ SuggestionSpan span) {
+ for (String suggestion : span.getSuggestions()) {
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.SUGGESTIONS, suggestion);
+ }
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.FLAGS, span.getFlags());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.LOCALE_STRING_FOR_COMPATIBILITY,
+ span.getLocale());
+ if (span.getLocaleObject() != null) {
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.LANGUAGE_TAG,
+ span.getLocaleObject().toLanguageTag());
+ }
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.HASH_CODE, span.hashCode());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.EASY_CORRECT_UNDERLINE_COLOR,
+ span.getEasyCorrectUnderlineColor());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.EASY_CORRECT_UNDERLINE_THICKNESS,
+ span.getEasyCorrectUnderlineThickness());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.MISSPELLED_UNDERLINE_COLOR,
+ span.getMisspelledUnderlineColor());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.MISSPELLED_UNDERLINE_THICKNESS,
+ span.getMisspelledUnderlineThickness());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.AUTO_CORRECTION_UNDERLINE_COLOR,
+ span.getAutoCorrectionUnderlineColor());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.AUTO_CORRECTION_UNDERLINE_THICKNESS,
+ span.getAutoCorrectionUnderlineThickness());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.GRAMMAR_ERROR_UNDERLINE_COLOR,
+ span.getGrammarErrorUnderlineColor());
+ out.write(RemoteViewsProto.CharSequence.Span.Suggestion.GRAMMAR_ERROR_UNDERLINE_THICKNESS,
+ span.getGrammarErrorUnderlineThickness());
+ }
+
+ public static SuperscriptSpan createSuperscriptSpanFromProto(@NonNull ProtoInputStream in) {
+ return new SuperscriptSpan();
+ }
+
+ public static void writeSuperscriptSpanToProto(@NonNull ProtoOutputStream out,
+ SuperscriptSpan span) {
+ }
+
+ public static TextAppearanceSpan createTextAppearanceSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ String familyName = null;
+ int style = 0;
+ int textSize = 0;
+ ColorStateList textColor = null;
+ ColorStateList textColorLink = null;
+ int textFontWeight = 0;
+ LocaleList textLocales = null;
+ float shadowRadius = 0F;
+ float shadowDx = 0F;
+ float shadowDy = 0F;
+ int shadowColor = 0;
+ boolean hasElegantTextHeight = false;
+ boolean elegantTextHeight = false;
+ boolean hasLetterSpacing = false;
+ float letterSpacing = 0F;
+ String fontFeatureSettings = null;
+ String fontVariationSettings = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME:
+ familyName = in.readString(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE:
+ style = in.readInt(RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE:
+ textSize = in.readInt(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR:
+ final long textColorToken = in.start(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR);
+ textColor = ColorStateList.createFromProto(in);
+ in.end(textColorToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK:
+ final long textColorLinkToken = in.start(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK);
+ textColorLink = ColorStateList.createFromProto(in);
+ in.end(textColorLinkToken);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT:
+ textFontWeight = in.readInt(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE:
+ textLocales = LocaleList.forLanguageTags(in.readString(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE));
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS:
+ shadowRadius = in.readFloat(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX:
+ shadowDx = in.readFloat(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY:
+ shadowDy = in.readFloat(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR:
+ shadowColor = in.readInt(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD:
+ hasElegantTextHeight = in.readBoolean(
+ RemoteViewsProto.CharSequence.Span
+ .TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT:
+ elegantTextHeight = in.readBoolean(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .TextAppearance.HAS_LETTER_SPACING_FIELD:
+ hasLetterSpacing = in.readBoolean(
+ RemoteViewsProto.CharSequence.Span
+ .TextAppearance.HAS_LETTER_SPACING_FIELD);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING:
+ letterSpacing = in.readFloat(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_FEATURE_SETTINGS:
+ fontFeatureSettings = in.readString(
+ RemoteViewsProto.CharSequence.Span
+ .TextAppearance.FONT_FEATURE_SETTINGS);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span
+ .TextAppearance.FONT_VARIATION_SETTINGS:
+ fontVariationSettings = in.readString(
+ RemoteViewsProto.CharSequence.Span
+ .TextAppearance.FONT_VARIATION_SETTINGS);
+ break;
+ default:
+ Log.w("TextAppearanceSpan",
+ "Unhandled field while reading TextAppearanceSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new TextAppearanceSpan(familyName, style, textSize, textColor, textColorLink,
+ /* typeface= */ null, textFontWeight, textLocales, shadowRadius, shadowDx, shadowDy,
+ shadowColor, hasElegantTextHeight, elegantTextHeight, hasLetterSpacing,
+ letterSpacing, fontFeatureSettings, fontVariationSettings);
+ }
+
+ public static void writeTextAppearanceSpanToProto(@NonNull ProtoOutputStream out,
+ TextAppearanceSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FAMILY_NAME, span.getFamily());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.STYLE, span.getTextStyle());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_SIZE, span.getTextSize());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_FONT_WEIGHT,
+ span.getTextFontWeight());
+ if (span.getTextLocales() != null) {
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_LOCALE,
+ span.getTextLocales().toLanguageTags());
+ }
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_RADIUS,
+ span.getShadowRadius());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DX, span.getShadowDx());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_DY, span.getShadowDy());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.SHADOW_COLOR,
+ span.getShadowColor());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.HAS_ELEGANT_TEXT_HEIGHT_FIELD,
+ span.hasElegantTextHeight());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.ELEGANT_TEXT_HEIGHT,
+ span.isElegantTextHeight());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.HAS_LETTER_SPACING_FIELD,
+ span.hasLetterSpacing());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.LETTER_SPACING,
+ span.getLetterSpacing());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_FEATURE_SETTINGS,
+ span.getFontFeatureSettings());
+ out.write(RemoteViewsProto.CharSequence.Span.TextAppearance.FONT_VARIATION_SETTINGS,
+ span.getFontVariationSettings());
+ if (span.getTextColor() != null) {
+ final long textColorToken = out.start(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR);
+ span.getTextColor().writeToProto(out);
+ out.end(textColorToken);
+ }
+ if (span.getLinkTextColor() != null) {
+ final long textColorLinkToken = out.start(
+ RemoteViewsProto.CharSequence.Span.TextAppearance.TEXT_COLOR_LINK);
+ span.getLinkTextColor().writeToProto(out);
+ out.end(textColorLinkToken);
+ }
+ }
+
+ public static TtsSpan createTtsSpanFromProto(@NonNull ProtoInputStream in) throws Exception {
+ String type = null;
+ PersistableBundle args = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Tts.TYPE:
+ type = in.readString(RemoteViewsProto.CharSequence.Span.Tts.TYPE);
+ break;
+ case (int) RemoteViewsProto.CharSequence.Span.Tts.ARGS:
+ final byte[] data = in.readString(
+ RemoteViewsProto.CharSequence.Span.Tts.ARGS).getBytes();
+ args = PersistableBundle.readFromStream(new ByteArrayInputStream(data));
+ break;
+ default:
+ Log.w("TtsSpan", "Unhandled field while reading TtsSpan " + "proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new TtsSpan(type, args);
+ }
+
+ public static void writeTtsSpanToProto(@NonNull ProtoOutputStream out, TtsSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Tts.TYPE, span.getType());
+ if (span.getArgs() != null) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ try {
+ span.getArgs().writeToStream(buf);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ out.write(RemoteViewsProto.CharSequence.Span.Tts.ARGS, buf.toString(UTF_8));
+ }
+ }
+
+ public static TypefaceSpan createTypefaceSpanFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ String family = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Typeface.FAMILY:
+ family = in.readString(RemoteViewsProto.CharSequence.Span.Typeface.FAMILY);
+ break;
+ default:
+ Log.w("TypefaceSpan", "Unhandled field while reading" + " TypefaceSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new TypefaceSpan(family);
+ }
+
+ public static void writeTypefaceSpanToProto(@NonNull ProtoOutputStream out, TypefaceSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Typeface.FAMILY, span.getFamily());
+ }
+
+ public static URLSpan createURLSpanFromProto(@NonNull ProtoInputStream in) throws Exception {
+ String url = null;
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.CharSequence.Span.Url.URL:
+ url = in.readString(RemoteViewsProto.CharSequence.Span.Url.URL);
+ break;
+ default:
+ Log.w("URLSpan", "Unhandled field while reading" + " URLSpan proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ return new URLSpan(url);
+ }
+
+ public static void writeURLSpanToProto(@NonNull ProtoOutputStream out, URLSpan span) {
+ out.write(RemoteViewsProto.CharSequence.Span.Url.URL, span.getURL());
+ }
+
+ public static UnderlineSpan createUnderlineSpanFromProto(@NonNull ProtoInputStream in) {
+ return new UnderlineSpan();
+ }
+
+ public static void writeUnderlineSpanToProto(@NonNull ProtoOutputStream out,
+ UnderlineSpan span) {
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ac899f4..61ecc62 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,10 +28,10 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import android.R;
import android.annotation.CallSuper;
@@ -937,6 +937,7 @@
private TextPaint mTempTextPaint;
private Object mTempCursor;
+ private Matrix mTempMatrix;
@UnsupportedAppUsage
private BoringLayout.Metrics mBoring;
@@ -12106,6 +12107,22 @@
}
private PointF convertFromScreenToContentCoordinates(PointF point) {
+ if (Flags.handwritingGestureWithTransformation()) {
+ if (mTempMatrix == null) {
+ mTempMatrix = new Matrix();
+ }
+ Matrix matrix = mTempMatrix;
+ matrix.reset();
+ transformMatrixToLocal(matrix);
+ matrix.postTranslate(
+ -viewportToContentHorizontalOffset(),
+ -viewportToContentVerticalOffset()
+ );
+
+ float[] copy = new float[] { point.x, point.y };
+ matrix.mapPoints(copy);
+ return new PointF(copy[0], copy[1]);
+ }
int[] screenToViewport = getLocationOnScreen();
PointF copy = new PointF(point);
copy.offset(
@@ -12115,6 +12132,22 @@
}
private RectF convertFromScreenToContentCoordinates(RectF rect) {
+ if (Flags.handwritingGestureWithTransformation()) {
+ if (mTempMatrix == null) {
+ mTempMatrix = new Matrix();
+ }
+ Matrix matrix = mTempMatrix;
+ matrix.reset();
+ transformMatrixToLocal(matrix);
+ matrix.postTranslate(
+ -viewportToContentHorizontalOffset(),
+ -viewportToContentVerticalOffset()
+ );
+
+ RectF copy = new RectF(rect);
+ matrix.mapRect(copy);
+ return copy;
+ }
int[] screenToViewport = getLocationOnScreen();
RectF copy = new RectF(rect);
copy.offset(
@@ -14279,6 +14312,9 @@
}
/**
+ * Don't use, it returns wrong result when the view is scaled. This method can be removed once
+ * Flags.handwritingGestureWithTransformation is enabled.
+ * Assume
* Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
* This method obtains the view's visible rectangle whereas the method
* {@link #getContentVisibleRect} returns the text layout's visible rectangle.
@@ -14299,6 +14335,8 @@
}
/**
+ * Don't use, it returns wrong result when view is scaled. This method can be removed once
+ * Flags.handwritingGestureWithTransformation is enabled.
* Helper method to set {@code rect} to the text content's non-clipped area in the view's
* coordinates.
*
@@ -14314,6 +14352,58 @@
getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
}
+ private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds,
+ @Nullable RectF handwritingBounds) {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ Rect rect = mTempRect;
+ if (!getGlobalVisibleRect(rect)) {
+ return false;
+ }
+ if (mTempMatrix == null) {
+ mTempMatrix = new Matrix();
+ }
+
+ Matrix matrix = mTempMatrix;
+ matrix.reset();
+ transformMatrixToLocal(matrix);
+ editorBounds.set(rect);
+ // When the view has transformations like scaleX/scaleY computing the global visible
+ // rectangle will already apply the transformations. The getLocalVisibleRect only offsets
+ // the global rectangle to local. And the result is wrong the View is scaled.
+ //
+ // This approach use the local transformation matrix to map the global rectangle to
+ // local instead.
+ //
+ // Note: it doesn't work well with rotation. Because Rect must be
+ // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's
+ // bounding box is stored at Rect instead. It makes the returned Rect larger than
+ // the correct size.
+ matrix.mapRect(editorBounds);
+
+ if (handwritingBounds != null) {
+ // Similar to editorBounds, handwritingBounds must be computed in global coordinates
+ // and then converted back to local coordinates. Otherwise, if the view is scaled,
+ // the handwritingBoundsOffsets are also scaled, which is not the expected behavior.
+ handwritingBounds.top = rect.top - getHandwritingBoundsOffsetTop();
+ handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft();
+ handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom();
+ handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight();
+ matrix.mapRect(handwritingBounds);
+ }
+ return true;
+ }
+
+ private boolean getContentVisibleRect(RectF rect) {
+ if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) {
+ return false;
+ }
+ // Clip the view's visible rect with the text layout's visible rect.
+ return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
+ getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
+ }
+
/**
* Populate requested character bounds in a {@link CursorAnchorInfo.Builder}
*
@@ -14333,9 +14423,15 @@
// character bounds in this case yet.
return;
}
- final Rect rect = new Rect();
- getContentVisibleRect(rect);
- final RectF visibleRect = new RectF(rect);
+ final RectF visibleRect = new RectF();
+
+ if (Flags.handwritingGestureWithTransformation()) {
+ getContentVisibleRect(visibleRect);
+ } else {
+ final Rect rect = new Rect();
+ getContentVisibleRect(rect);
+ visibleRect.set(rect);
+ }
final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
@@ -14438,24 +14534,26 @@
builder.setMatrix(viewToScreenMatrix);
if (includeEditorBounds) {
- if (mTempRect == null) {
- mTempRect = new Rect();
- }
- final Rect bounds = mTempRect;
- final RectF editorBounds;
- final RectF handwritingBounds;
- if (getViewVisibleRect(bounds)) {
- editorBounds = new RectF(bounds);
- handwritingBounds = new RectF(editorBounds);
- handwritingBounds.top -= getHandwritingBoundsOffsetTop();
- handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
- handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
- handwritingBounds.right += getHandwritingBoundsOffsetRight();
+ final RectF editorBounds = new RectF();
+ final RectF handwritingBounds = new RectF();
+ if (Flags.handwritingGestureWithTransformation()) {
+ getEditorAndHandwritingBounds(editorBounds, handwritingBounds);
} else {
- // The editor is not visible at all, return empty rectangles. We still need to
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect bounds = mTempRect;
+
+ // If the editor is not visible at all, return empty rectangles. We still need to
// return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
- editorBounds = new RectF();
- handwritingBounds = new RectF();
+ if (getViewVisibleRect(bounds)) {
+ editorBounds.set(bounds);
+ handwritingBounds.set(editorBounds);
+ handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+ handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+ handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+ handwritingBounds.right += getHandwritingBoundsOffsetRight();
+ }
}
EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
@@ -14533,29 +14631,57 @@
}
if (includeVisibleLineBounds) {
- final Rect visibleRect = new Rect();
- if (getContentVisibleRect(visibleRect)) {
- // Subtract the viewportToContentVerticalOffset to convert the view
- // coordinates to layout coordinates.
- final float visibleTop =
- visibleRect.top - viewportToContentVerticalOffset;
- final float visibleBottom =
- visibleRect.bottom - viewportToContentVerticalOffset;
- final int firstLine =
- layout.getLineForVertical((int) Math.floor(visibleTop));
- final int lastLine =
- layout.getLineForVertical((int) Math.ceil(visibleBottom));
+ if (Flags.handwritingGestureWithTransformation()) {
+ RectF visibleRect = new RectF();
+ if (getContentVisibleRect(visibleRect)) {
+ // Subtract the viewportToContentVerticalOffset to convert the view
+ // coordinates to layout coordinates.
+ final float visibleTop =
+ visibleRect.top - viewportToContentVerticalOffset;
+ final float visibleBottom =
+ visibleRect.bottom - viewportToContentVerticalOffset;
+ final int firstLine =
+ layout.getLineForVertical((int) Math.floor(visibleTop));
+ final int lastLine =
+ layout.getLineForVertical((int) Math.ceil(visibleBottom));
- for (int line = firstLine; line <= lastLine; ++line) {
- final float left = layout.getLineLeft(line)
- + viewportToContentHorizontalOffset;
- final float top = layout.getLineTop(line)
- + viewportToContentVerticalOffset;
- final float right = layout.getLineRight(line)
- + viewportToContentHorizontalOffset;
- final float bottom = layout.getLineBottom(line, false)
- + viewportToContentVerticalOffset;
- builder.addVisibleLineBounds(left, top, right, bottom);
+ for (int line = firstLine; line <= lastLine; ++line) {
+ final float left = layout.getLineLeft(line)
+ + viewportToContentHorizontalOffset;
+ final float top = layout.getLineTop(line)
+ + viewportToContentVerticalOffset;
+ final float right = layout.getLineRight(line)
+ + viewportToContentHorizontalOffset;
+ final float bottom = layout.getLineBottom(line, false)
+ + viewportToContentVerticalOffset;
+ builder.addVisibleLineBounds(left, top, right, bottom);
+ }
+ }
+ } else {
+ final Rect visibleRect = new Rect();
+ if (getContentVisibleRect(visibleRect)) {
+ // Subtract the viewportToContentVerticalOffset to convert the view
+ // coordinates to layout coordinates.
+ final float visibleTop =
+ visibleRect.top - viewportToContentVerticalOffset;
+ final float visibleBottom =
+ visibleRect.bottom - viewportToContentVerticalOffset;
+ final int firstLine =
+ layout.getLineForVertical((int) Math.floor(visibleTop));
+ final int lastLine =
+ layout.getLineForVertical((int) Math.ceil(visibleBottom));
+
+ for (int line = firstLine; line <= lastLine; ++line) {
+ final float left = layout.getLineLeft(line)
+ + viewportToContentHorizontalOffset;
+ final float top = layout.getLineTop(line)
+ + viewportToContentVerticalOffset;
+ final float right = layout.getLineRight(line)
+ + viewportToContentHorizontalOffset;
+ final float bottom = layout.getLineBottom(line, false)
+ + viewportToContentVerticalOffset;
+ builder.addVisibleLineBounds(left, top, right, bottom);
+ }
}
}
}
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index f0144cb..20d1b3b 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -72,6 +72,7 @@
int mAppearance;
private final boolean mIsTranslucent;
private final boolean mHasImeSurface;
+ private final int mUiMode;
// Must be one of the named color spaces, otherwise, always use SRGB color space.
private final ColorSpace mColorSpace;
private int mInternalReferences;
@@ -96,7 +97,7 @@
Rect contentInsets, Rect letterboxInsets, boolean isLowResolution,
boolean isRealSnapshot, int windowingMode,
@WindowInsetsController.Appearance int appearance, boolean isTranslucent,
- boolean hasImeSurface) {
+ boolean hasImeSurface, int uiMode) {
mId = id;
mCaptureTime = captureTime;
mTopActivityComponent = topActivityComponent;
@@ -114,6 +115,7 @@
mAppearance = appearance;
mIsTranslucent = isTranslucent;
mHasImeSurface = hasImeSurface;
+ mUiMode = uiMode;
}
private TaskSnapshot(Parcel source) {
@@ -136,6 +138,7 @@
mAppearance = source.readInt();
mIsTranslucent = source.readBoolean();
mHasImeSurface = source.readBoolean();
+ mUiMode = source.readInt();
}
/**
@@ -273,6 +276,13 @@
return mAppearance;
}
+ /**
+ * @return The uiMode the screenshot was taken in.
+ */
+ public int getUiMode() {
+ return mUiMode;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -295,6 +305,7 @@
dest.writeInt(mAppearance);
dest.writeBoolean(mIsTranslucent);
dest.writeBoolean(mHasImeSurface);
+ dest.writeInt(mUiMode);
}
@Override
@@ -318,7 +329,8 @@
+ " mAppearance=" + mAppearance
+ " mIsTranslucent=" + mIsTranslucent
+ " mHasImeSurface=" + mHasImeSurface
- + " mInternalReferences=" + mInternalReferences;
+ + " mInternalReferences=" + mInternalReferences
+ + " mUiMode=" + Integer.toHexString(mUiMode);
}
/**
@@ -370,6 +382,7 @@
private boolean mIsTranslucent;
private boolean mHasImeSurface;
private int mPixelFormat;
+ private int mUiMode;
public Builder setId(long id) {
mId = id;
@@ -452,6 +465,14 @@
return this;
}
+ /**
+ * Sets the original uiMode while capture
+ */
+ public Builder setUiMode(int uiMode) {
+ mUiMode = uiMode;
+ return this;
+ }
+
public int getPixelFormat() {
return mPixelFormat;
}
@@ -481,7 +502,8 @@
mWindowingMode,
mAppearance,
mIsTranslucent,
- mHasImeSurface);
+ mHasImeSurface,
+ mUiMode);
}
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index e9d77f8..314bf89 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -700,6 +700,18 @@
}
/**
+ * Restore the back navigation target from visible to invisible for canceling gesture animation.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction restoreBackNavi() {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+ /**
* Adds a given {@code Rect} as an insets source frame on the {@code receiver}.
*
* @param receiver The window container that the insets source is added to.
@@ -1436,6 +1448,7 @@
public static final int HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION = 17;
public static final int HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK = 18;
public static final int HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE = 19;
+ public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 125a0b2..4f84817 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -218,3 +218,10 @@
description: "Enables desktop windowing app-to-web education"
bug: "348205896"
}
+
+flag {
+ name: "enable_minimize_button"
+ namespace: "lse_desktop_experience"
+ description: "Adds a minimize button the the caption bar"
+ bug: "356843241"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index d5746e5..9aeccf4 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -65,6 +65,14 @@
}
flag {
+ name: "keyguard_going_away_timeout"
+ namespace: "windowing_frontend"
+ description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting"
+ bug: "343598832"
+ is_fixed_read_only: true
+}
+
+flag {
name: "close_to_square_config_includes_status_bar"
namespace: "windowing_frontend"
description: "On close to square display, when necessary, configuration includes status bar"
@@ -72,6 +80,17 @@
}
flag {
+ name: "reduce_keyguard_transitions"
+ namespace: "windowing_frontend"
+ description: "Avoid setting keyguard transitions ready unless there are no other changes"
+ bug: "354647472"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "transit_ready_tracking"
namespace: "windowing_frontend"
description: "Enable accurate transition readiness tracking"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b8c2a5f..a6ae948 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -19,13 +19,6 @@
flag {
namespace: "windowing_sdk"
- name: "fullscreen_dim_flag"
- description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
- bug: "293797706"
-}
-
-flag {
- namespace: "windowing_sdk"
name: "activity_embedding_interactive_divider_flag"
description: "Whether the interactive divider feature is enabled"
bug: "293654166"
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index f72a5ca..f210741 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -21,7 +21,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.graphics.Color;
-
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import com.android.internal.graphics.cam.Cam;
/**
@@ -29,6 +29,7 @@
*
* A set of color-related utility methods, building upon those available in {@code Color}.
*/
+@RavenwoodKeepWholeClass
public final class ColorUtils {
private static final double XYZ_WHITE_REFERENCE_X = 95.047;
@@ -696,4 +697,4 @@
double calculateContrast(int foreground, int background, int alpha);
}
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/graphics/cam/Cam.java b/core/java/com/android/internal/graphics/cam/Cam.java
index 1df85c3..49fa37b 100644
--- a/core/java/com/android/internal/graphics/cam/Cam.java
+++ b/core/java/com/android/internal/graphics/cam/Cam.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import com.android.internal.graphics.ColorUtils;
@@ -25,6 +26,7 @@
* A color appearance model, based on CAM16, extended to use L* as the lightness dimension, and
* coupled to a gamut mapping algorithm. Creates a color system, enables a digital design system.
*/
+@RavenwoodKeepWholeClass
public class Cam {
// The maximum difference between the requested L* and the L* returned.
private static final float DL_MAX = 0.2f;
diff --git a/core/java/com/android/internal/graphics/cam/CamUtils.java b/core/java/com/android/internal/graphics/cam/CamUtils.java
index f541729..76fabc6 100644
--- a/core/java/com/android/internal/graphics/cam/CamUtils.java
+++ b/core/java/com/android/internal/graphics/cam/CamUtils.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.graphics.Color;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import com.android.internal.graphics.ColorUtils;
@@ -45,6 +46,7 @@
* consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of
* Color Psychology, 2015
*/
+@RavenwoodKeepWholeClass
public final class CamUtils {
private CamUtils() {
}
diff --git a/core/java/com/android/internal/graphics/cam/Frame.java b/core/java/com/android/internal/graphics/cam/Frame.java
index 0ac7cbc..c419fab 100644
--- a/core/java/com/android/internal/graphics/cam/Frame.java
+++ b/core/java/com/android/internal/graphics/cam/Frame.java
@@ -17,6 +17,7 @@
package com.android.internal.graphics.cam;
import android.annotation.NonNull;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.util.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +34,7 @@
* number of calculations during the color => CAM conversion process that depend only on the viewing
* conditions. Caching those calculations in a Frame instance saves a significant amount of time.
*/
+@RavenwoodKeepWholeClass
public final class Frame {
// Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar,
// Motta - A Standard Default Color Space for the Internet: sRGB, 1996.
diff --git a/core/java/com/android/internal/graphics/cam/HctSolver.java b/core/java/com/android/internal/graphics/cam/HctSolver.java
index d7a8691..6e558e7 100644
--- a/core/java/com/android/internal/graphics/cam/HctSolver.java
+++ b/core/java/com/android/internal/graphics/cam/HctSolver.java
@@ -16,6 +16,8 @@
package com.android.internal.graphics.cam;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
/**
* An efficient algorithm for determining the closest sRGB color to a set of HCT coordinates,
* based on geometrical insights for finding intersections in linear RGB, CAM16, and L*a*b*.
@@ -24,6 +26,7 @@
* Copied from //java/com/google/ux/material/libmonet/hct on May 22 2022.
* ColorUtils/MathUtils functions that were required were added to CamUtils.
*/
+@RavenwoodKeepWholeClass
public class HctSolver {
private HctSolver() {}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 2daf0fd..921363c 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -108,7 +108,6 @@
* @param backDisposition disposition flags
* @see android.inputmethodservice.InputMethodService#IME_ACTIVE
* @see android.inputmethodservice.InputMethodService#IME_VISIBLE
- * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
* @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
* @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
*/
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index b316a01..12d3264 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -727,11 +727,11 @@
this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries,
TextUtils.safeIntern(libraryName));
this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
- this.usesSdkLibrariesVersionsMajor, versionMajor, true);
+ this.usesSdkLibrariesVersionsMajor, versionMajor, /* allowDuplicates= */ true);
this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
- this.usesSdkLibrariesCertDigests, certSha256Digests, true);
- this.usesSdkLibrariesOptional = ArrayUtils.appendBoolean(this.usesSdkLibrariesOptional,
- usesSdkLibrariesOptional);
+ this.usesSdkLibrariesCertDigests, certSha256Digests, /* allowDuplicates= */ true);
+ this.usesSdkLibrariesOptional = ArrayUtils.appendBooleanDuplicatesAllowed(
+ this.usesSdkLibrariesOptional, usesSdkLibrariesOptional);
return this;
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbec1f1..e0c90d8 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -332,6 +332,8 @@
}
private void onTracingFlush() {
+ Log.d(LOG_TAG, "Executing onTracingFlush");
+
final ExecutorService loggingService;
try {
mBackgroundServiceLock.lock();
@@ -352,15 +354,19 @@
Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
}
- dumpTransitionTraceConfig();
+ dumpViewerConfig();
+
+ Log.d(LOG_TAG, "Finished onTracingFlush");
}
- private void dumpTransitionTraceConfig() {
+ private void dumpViewerConfig() {
if (mViewerConfigInputStreamProvider == null) {
// No viewer config available
return;
}
+ Log.d(LOG_TAG, "Dumping viewer config to trace");
+
ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
if (pis == null) {
@@ -390,6 +396,8 @@
Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
}
});
+
+ Log.d(LOG_TAG, "Dumped viewer config to trace");
}
private static void writeViewerConfigGroup(
@@ -770,6 +778,8 @@
private synchronized void onTracingInstanceStart(
int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+ Log.d(LOG_TAG, "Executing onTracingInstanceStart");
+
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
mDefaultLogLevelCounts[i]++;
@@ -800,10 +810,13 @@
mCacheUpdater.run();
this.mTracingInstances.incrementAndGet();
+
+ Log.d(LOG_TAG, "Finished onTracingInstanceStart");
}
private synchronized void onTracingInstanceStop(
int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+ Log.d(LOG_TAG, "Executing onTracingInstanceStop");
this.mTracingInstances.decrementAndGet();
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
@@ -835,6 +848,7 @@
}
mCacheUpdater.run();
+ Log.d(LOG_TAG, "Finished onTracingInstanceStop");
}
private static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
new file mode 100644
index 0000000..3dab2e3
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProtoLogCommandHandler.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class ProtoLogCommandHandler extends ShellCommand {
+ @NonNull
+ private final ProtoLogService mProtoLogService;
+ @Nullable
+ private final PrintWriter mPrintWriter;
+
+ public ProtoLogCommandHandler(@NonNull ProtoLogService protoLogService) {
+ this(protoLogService, null);
+ }
+
+ @VisibleForTesting
+ public ProtoLogCommandHandler(
+ @NonNull ProtoLogService protoLogService, @Nullable PrintWriter printWriter) {
+ this.mProtoLogService = protoLogService;
+ this.mPrintWriter = printWriter;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ onHelp();
+ return 0;
+ }
+
+ return switch (cmd) {
+ case "groups" -> handleGroupsCommands(getNextArg());
+ case "logcat" -> handleLogcatCommands(getNextArg());
+ default -> handleDefaultCommands(cmd);
+ };
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("ProtoLog commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" groups (list | status)");
+ pw.println(" list - lists all ProtoLog groups registered with ProtoLog service");
+ pw.println(" status <group> - print the status of a ProtoLog group");
+ pw.println();
+ pw.println(" logcat (enable | disable) <group>");
+ pw.println(" enable or disable ProtoLog to logcat");
+ pw.println();
+ }
+
+ @NonNull
+ @Override
+ public PrintWriter getOutPrintWriter() {
+ if (mPrintWriter != null) {
+ return mPrintWriter;
+ }
+
+ return super.getOutPrintWriter();
+ }
+
+ private int handleGroupsCommands(@Nullable String cmd) {
+ PrintWriter pw = getOutPrintWriter();
+
+ if (cmd == null) {
+ pw.println("Incomplete command. Use 'cmd protolog help' for guidance.");
+ return 0;
+ }
+
+ switch (cmd) {
+ case "list": {
+ final String[] availableGroups = mProtoLogService.getGroups();
+ if (availableGroups.length == 0) {
+ pw.println("No ProtoLog groups registered with ProtoLog service.");
+ return 0;
+ }
+
+ pw.println("ProtoLog groups registered with service:");
+ for (String group : availableGroups) {
+ pw.println("- " + group);
+ }
+
+ return 0;
+ }
+ case "status": {
+ final String group = getNextArg();
+
+ if (group == null) {
+ pw.println("Incomplete command. Use 'cmd protolog help' for guidance.");
+ return 0;
+ }
+
+ pw.println("ProtoLog group " + group + "'s status:");
+
+ if (!Set.of(mProtoLogService.getGroups()).contains(group)) {
+ pw.println("UNREGISTERED");
+ return 0;
+ }
+
+ pw.println("LOG_TO_LOGCAT = " + mProtoLogService.isLoggingToLogcat(group));
+ return 0;
+ }
+ default: {
+ pw.println("Unknown command: " + cmd);
+ return -1;
+ }
+ }
+ }
+
+ private int handleLogcatCommands(@Nullable String cmd) {
+ PrintWriter pw = getOutPrintWriter();
+
+ if (cmd == null || peekNextArg() == null) {
+ pw.println("Incomplete command. Use 'cmd protolog help' for guidance.");
+ return 0;
+ }
+
+ switch (cmd) {
+ case "enable" -> {
+ mProtoLogService.enableProtoLogToLogcat(processGroups());
+ return 0;
+ }
+ case "disable" -> {
+ mProtoLogService.disableProtoLogToLogcat(processGroups());
+ return 0;
+ }
+ default -> {
+ pw.println("Unknown command: " + cmd);
+ return -1;
+ }
+ }
+ }
+
+ @NonNull
+ private String[] processGroups() {
+ if (getRemainingArgsCount() == 0) {
+ return mProtoLogService.getGroups();
+ }
+
+ final List<String> groups = new ArrayList<>();
+ while (getRemainingArgsCount() > 0) {
+ groups.add(getNextArg());
+ }
+
+ return groups.toArray(new String[0]);
+ }
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogService.java b/core/java/com/android/internal/protolog/ProtoLogService.java
new file mode 100644
index 0000000..2333a06
--- /dev/null
+++ b/core/java/com/android/internal/protolog/ProtoLogService.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG;
+import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.util.Log;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing
+ * system. Currently this service has the following roles:
+ * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat.
+ * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog
+ * clients. This is for two reasons: firstly, because client processes might be frozen so might
+ * not response to the request to dump their viewer config when the trace is stopped; secondly,
+ * multiple processes might be running the same code with the same viewer config, this centralized
+ * service ensures we don't dump the same viewer config multiple times across processes.
+ * <p>
+ * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to
+ * this service on initialization.
+ * <p>
+ * This service is intended to run on the system server, such that it never gets frozen.
+ */
+@SystemService(Context.PROTOLOG_SERVICE)
+public final class ProtoLogService extends IProtoLogService.Stub {
+ private static final String LOG_TAG = "ProtoLogService";
+
+ private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
+ this::onTracingInstanceStart,
+ this::onTracingInstanceFlush,
+ this::onTracingInstanceStop
+ );
+
+ /**
+ * Keeps track of how many of each viewer config file is currently registered.
+ * Use to keep track of which viewer config files are actively being used in tracing and might
+ * need to be dumped on flush.
+ */
+ private final Map<String, Integer> mConfigFileCounts = new HashMap<>();
+ /**
+ * Keeps track of the viewer config file of each client if available.
+ */
+ private final Map<IProtoLogClient, String> mClientConfigFiles = new HashMap<>();
+
+ /**
+ * Keeps track of all the protolog groups that have been registered by clients and are still
+ * being actively traced.
+ */
+ private final Set<String> mRegisteredGroups = new HashSet<>();
+ /**
+ * Keeps track of all the clients that are actively tracing a given protolog group.
+ */
+ private final Map<String, Set<IProtoLogClient>> mGroupToClients = new HashMap<>();
+
+ /**
+ * Keeps track of whether or not a given group should be logged to logcat.
+ * True when logging to logcat, false otherwise.
+ */
+ private final Map<String, Boolean> mLogGroupToLogcatStatus = new TreeMap<>();
+
+ /**
+ * Keeps track of all the tracing instance ids that are actively running for ProtoLog.
+ */
+ private final Set<Integer> mRunningInstances = new HashSet<>();
+
+ private final ViewerConfigFileTracer mViewerConfigFileTracer;
+
+ public ProtoLogService() {
+ this(ProtoLogService::dumpTransitionTraceConfig);
+ }
+
+ @VisibleForTesting
+ public ProtoLogService(@NonNull ViewerConfigFileTracer tracer) {
+ // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be
+ // receive the lifecycle callbacks of the datasource and write the viewer configs if and
+ // when required to the datasource.
+ Producer.init(InitArguments.DEFAULTS);
+ final var params = new DataSourceParams.Builder()
+ .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
+ .build();
+ mDataSource.register(params);
+
+ mViewerConfigFileTracer = tracer;
+ }
+
+ public static class RegisterClientArgs extends IRegisterClientArgs.Stub {
+ /**
+ * The viewer config file to be registered for this client ProtoLog process.
+ */
+ @Nullable
+ private String mViewerConfigFile = null;
+ /**
+ * The list of all groups that this client protolog process supports and might trace.
+ */
+ @NonNull
+ private String[] mGroups = new String[0];
+ /**
+ * The default logcat status of the ProtoLog client. True is logging to logcat, false
+ * otherwise. The indices should match the indices in {@link mGroups}.
+ */
+ @NonNull
+ private boolean[] mLogcatStatus = new boolean[0];
+
+ public record GroupConfig(@NonNull String group, boolean logToLogcat) {}
+
+ /**
+ * Specify groups to register with this client that will be used for protologging in this
+ * process.
+ * @param groups to register with this client.
+ * @return self
+ */
+ public RegisterClientArgs setGroups(GroupConfig... groups) {
+ mGroups = new String[groups.length];
+ mLogcatStatus = new boolean[groups.length];
+
+ for (int i = 0; i < groups.length; i++) {
+ mGroups[i] = groups[i].group;
+ mLogcatStatus[i] = groups[i].logToLogcat;
+ }
+
+ return this;
+ }
+
+ /**
+ * Set the viewer config file that the logs in this process are using.
+ * @param viewerConfigFile The file path of the viewer config.
+ * @return self
+ */
+ public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) {
+ mViewerConfigFile = viewerConfigFile;
+
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String[] getGroups() {
+ return mGroups;
+ }
+
+ @Override
+ @NonNull
+ public boolean[] getGroupsDefaultLogcatStatus() {
+ return mLogcatStatus;
+ }
+
+ @Nullable
+ @Override
+ public String getViewerConfigFile() {
+ return mViewerConfigFile;
+ }
+ }
+
+ @FunctionalInterface
+ public interface ViewerConfigFileTracer {
+ /**
+ * Write the viewer config data to the trace buffer.
+ *
+ * @param dataSource The target datasource to write the viewer config to.
+ * @param viewerConfigFilePath The path of the viewer config file which contains the data we
+ * want to write to the trace buffer.
+ * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
+ */
+ void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath)
+ throws FileNotFoundException;
+ }
+
+ @Override
+ public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args)
+ throws RemoteException {
+ client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0);
+
+ final String viewerConfigFile = args.getViewerConfigFile();
+ if (viewerConfigFile != null) {
+ registerViewerConfigFile(client, viewerConfigFile);
+ }
+
+ registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus());
+ }
+
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ new ProtoLogCommandHandler(this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ /**
+ * Get the list of groups clients have registered to the protolog service.
+ * @return The list of ProtoLog groups registered with this service.
+ */
+ @NonNull
+ public String[] getGroups() {
+ return mRegisteredGroups.toArray(new String[0]);
+ }
+
+ /**
+ * Enable logging target groups to logcat.
+ * @param groups we want to enable logging them to logcat for.
+ */
+ public void enableProtoLogToLogcat(String... groups) {
+ toggleProtoLogToLogcat(true, groups);
+ }
+
+ /**
+ * Disable logging target groups to logcat.
+ * @param groups we want to disable from being logged to logcat.
+ */
+ public void disableProtoLogToLogcat(String... groups) {
+ toggleProtoLogToLogcat(false, groups);
+ }
+
+ /**
+ * Check if a group is logging to logcat
+ * @param group The group we want to check for
+ * @return True iff we are logging this group to logcat.
+ */
+ public boolean isLoggingToLogcat(@NonNull String group) {
+ final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group);
+
+ if (isLoggingToLogcat == null) {
+ throw new RuntimeException(
+ "Trying to get logcat logging status of non-registered group " + group);
+ }
+
+ return isLoggingToLogcat;
+ }
+
+ private void registerViewerConfigFile(
+ @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) {
+ final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0);
+ mConfigFileCounts.put(viewerConfigFile, count + 1);
+ mClientConfigFiles.put(client, viewerConfigFile);
+ }
+
+ private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups,
+ @NonNull boolean[] logcatStatuses) throws RemoteException {
+ if (groups.length != logcatStatuses.length) {
+ throw new RuntimeException(
+ "Expected groups and logcatStatuses to have the same length, "
+ + "but groups has length " + groups.length
+ + " and logcatStatuses has length " + logcatStatuses.length);
+ }
+
+ for (int i = 0; i < groups.length; i++) {
+ String group = groups[i];
+ boolean logcatStatus = logcatStatuses[i];
+
+ mRegisteredGroups.add(group);
+
+ mGroupToClients.putIfAbsent(group, new HashSet<>());
+ mGroupToClients.get(group).add(client);
+
+ if (!mLogGroupToLogcatStatus.containsKey(group)) {
+ mLogGroupToLogcatStatus.put(group, logcatStatus);
+ }
+
+ boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group);
+ if (requestedLogToLogcat != logcatStatus) {
+ client.toggleLogcat(requestedLogToLogcat, new String[] { group });
+ }
+ }
+ }
+
+ private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) {
+ final var clientToGroups = new HashMap<IProtoLogClient, Set<String>>();
+
+ for (String group : groups) {
+ final var clients = mGroupToClients.get(group);
+
+ if (clients == null) {
+ // No clients associated to this group
+ Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group
+ + " with no registered clients.");
+ continue;
+ }
+
+ for (IProtoLogClient client : clients) {
+ clientToGroups.putIfAbsent(client, new HashSet<>());
+ clientToGroups.get(client).add(group);
+ }
+ }
+
+ for (IProtoLogClient client : clientToGroups.keySet()) {
+ try {
+ client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0]));
+ } catch (RemoteException e) {
+ throw new RuntimeException(
+ "Failed to toggle logcat status for groups on client", e);
+ }
+ }
+
+ for (String group : groups) {
+ mLogGroupToLogcatStatus.put(group, enabled);
+ }
+ }
+
+ private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+ mRunningInstances.add(instanceIdx);
+ }
+
+ private void onTracingInstanceFlush() {
+ for (String fileName : mConfigFileCounts.keySet()) {
+ try {
+ mViewerConfigFileTracer.trace(mDataSource, fileName);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) {
+ mRunningInstances.remove(instanceIdx);
+ }
+
+ private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
+ @NonNull String viewerConfigFilePath) throws FileNotFoundException {
+ final var pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+
+ dataSource.trace(ctx -> {
+ try {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+
+ final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) MESSAGES -> writeViewerConfigMessage(pis, os);
+ case (int) GROUPS -> writeViewerConfigGroup(pis, os);
+ }
+ }
+
+ os.end(outProtologViewerConfigToken);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e);
+ }
+ });
+ }
+
+ private void onClientBinderDeath(@NonNull IProtoLogClient client) {
+ // Dump the tracing config now if no other client is going to dump the same config file.
+ String configFile = mClientConfigFiles.get(client);
+ if (configFile != null) {
+ final var newCount = mConfigFileCounts.get(configFile) - 1;
+ mConfigFileCounts.put(configFile, newCount);
+ boolean lastProcessWithViewerConfig = newCount == 0;
+ if (lastProcessWithViewerConfig) {
+ try {
+ mViewerConfigFileTracer.trace(mDataSource, configFile);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private static void writeViewerConfigGroup(
+ @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+ final long inGroupToken = pis.start(GROUPS);
+ final long outGroupToken = os.start(GROUPS);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID -> {
+ int id = pis.readInt(ID);
+ os.write(ID, id);
+ }
+ case (int) NAME -> {
+ String name = pis.readString(NAME);
+ os.write(NAME, name);
+ }
+ case (int) TAG -> {
+ String tag = pis.readString(TAG);
+ os.write(TAG, tag);
+ }
+ default ->
+ throw new RuntimeException(
+ "Unexpected field id " + pis.getFieldNumber());
+ }
+ }
+
+ pis.end(inGroupToken);
+ os.end(outGroupToken);
+ }
+
+ private static void writeViewerConfigMessage(
+ @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException {
+ final long inMessageToken = pis.start(MESSAGES);
+ final long outMessagesToken = os.start(MESSAGES);
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) MESSAGE_ID -> os.write(MESSAGE_ID,
+ pis.readLong(MESSAGE_ID));
+ case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE));
+ case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL));
+ case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID));
+ default ->
+ throw new RuntimeException(
+ "Unexpected field id " + pis.getFieldNumber());
+ }
+ }
+
+ pis.end(inMessageToken);
+ os.end(outMessagesToken);
+ }
+}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 8f00f79..1e2cad4 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -620,10 +620,10 @@
}
/**
- * Adds value to given array if not already present, providing set-like
- * behavior.
+ * Adds value to given array. The method allows duplicate values.
*/
- public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) {
+ public static boolean[] appendBooleanDuplicatesAllowed(@Nullable boolean[] cur,
+ boolean val) {
if (cur == null) {
return new boolean[] { val };
}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
index 15ecedd..cd7dcfd 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
@@ -29,7 +29,7 @@
import android.util.IntArray;
import android.util.LongArray;
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java
similarity index 86%
rename from core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java
rename to core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java
index 23df304..6c562c9 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedVibrationEffect.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedComposedEffect.java
@@ -29,24 +29,24 @@
import java.util.Arrays;
/**
- * Serialized representation of a {@link VibrationEffect}.
+ * Serialized representation of a {@link VibrationEffect.Composed}.
*
* <p>The vibration is represented by a list of serialized segments that can be added to a
* {@link VibrationEffect.Composition} during the {@link #deserialize()} procedure.
*
* @hide
*/
-final class SerializedVibrationEffect implements XmlSerializedVibration<VibrationEffect> {
+final class SerializedComposedEffect implements XmlSerializedVibration<VibrationEffect.Composed> {
@NonNull
private final SerializedSegment[] mSegments;
- SerializedVibrationEffect(@NonNull SerializedSegment segment) {
+ SerializedComposedEffect(@NonNull SerializedSegment segment) {
requireNonNull(segment);
mSegments = new SerializedSegment[]{ segment };
}
- SerializedVibrationEffect(@NonNull SerializedSegment[] segments) {
+ SerializedComposedEffect(@NonNull SerializedSegment[] segments) {
requireNonNull(segments);
checkArgument(segments.length > 0, "Unsupported empty vibration");
mSegments = segments;
@@ -54,12 +54,12 @@
@NonNull
@Override
- public VibrationEffect deserialize() {
+ public VibrationEffect.Composed deserialize() {
VibrationEffect.Composition composition = VibrationEffect.startComposition();
for (SerializedSegment segment : mSegments) {
segment.deserializeIntoComposition(composition);
}
- return composition.compose();
+ return (VibrationEffect.Composed) composition.compose();
}
@Override
@@ -79,7 +79,7 @@
@Override
public String toString() {
- return "SerializedVibrationEffect{"
+ return "SerializedComposedEffect{"
+ "segments=" + Arrays.toString(mSegments)
+ '}';
}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
index db5c7ff..862f7cb 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedCompositionPrimitive.java
@@ -27,7 +27,7 @@
import android.os.VibrationEffect;
import android.os.vibrator.PrimitiveSegment;
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
index 8924311..a6f48a4 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedPredefinedEffect.java
@@ -25,7 +25,7 @@
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java
new file mode 100644
index 0000000..aa1b0a23
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedVendorEffect.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.PersistableBundle;
+import android.os.VibrationEffect;
+import android.text.TextUtils;
+import android.util.Base64;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Serialized representation of a {@link VibrationEffect.VendorEffect}.
+ *
+ * <p>The vibration is represented by an opaque {@link PersistableBundle} that can be used by
+ * {@link VibrationEffect#createVendorEffect(PersistableBundle)} during the {@link #deserialize()}
+ * procedure.
+ *
+ * @hide
+ */
+final class SerializedVendorEffect implements XmlSerializedVibration<VibrationEffect.VendorEffect> {
+
+ @NonNull
+ private final PersistableBundle mVendorData;
+
+ SerializedVendorEffect(@NonNull PersistableBundle vendorData) {
+ requireNonNull(vendorData);
+ mVendorData = vendorData;
+ }
+
+ @SuppressLint("MissingPermission")
+ @NonNull
+ @Override
+ public VibrationEffect.VendorEffect deserialize() {
+ return (VibrationEffect.VendorEffect) VibrationEffect.createVendorEffect(mVendorData);
+ }
+
+ @Override
+ public void write(@NonNull TypedXmlSerializer serializer)
+ throws IOException {
+ serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION_EFFECT);
+ writeContent(serializer);
+ serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VIBRATION_EFFECT);
+ }
+
+ @Override
+ public void writeContent(@NonNull TypedXmlSerializer serializer) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ mVendorData.writeToStream(outputStream);
+
+ serializer.startTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VENDOR_EFFECT);
+ serializer.text(Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP));
+ serializer.endTag(XmlConstants.NAMESPACE, XmlConstants.TAG_VENDOR_EFFECT);
+ }
+
+ @Override
+ public String toString() {
+ return "SerializedVendorEffect{"
+ + "vendorData=" + mVendorData
+ + '}';
+ }
+
+ /** Parser implementation for {@link SerializedVendorEffect}. */
+ static final class Parser {
+
+ @NonNull
+ static SerializedVendorEffect parseNext(@NonNull TypedXmlPullParser parser,
+ @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+ XmlValidator.checkStartTag(parser, TAG_VENDOR_EFFECT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+ PersistableBundle vendorData;
+ XmlReader.readNextText(parser, TAG_VENDOR_EFFECT);
+
+ try {
+ String text = parser.getText().trim();
+ XmlValidator.checkParserCondition(!text.isEmpty(),
+ "Expected tag %s to have base64 representation of vendor data, got empty",
+ TAG_VENDOR_EFFECT);
+
+ vendorData = PersistableBundle.readFromStream(
+ new ByteArrayInputStream(Base64.decode(text, Base64.DEFAULT)));
+ XmlValidator.checkParserCondition(!vendorData.isEmpty(),
+ "Expected tag %s to have non-empty vendor data, got empty bundle",
+ TAG_VENDOR_EFFECT);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ throw new XmlParserException(
+ TextUtils.formatSimple(
+ "Expected base64 representation of vendor data in tag %s, got %s",
+ TAG_VENDOR_EFFECT, parser.getText()),
+ e);
+ } catch (IOException e) {
+ throw new XmlParserException("Error reading vendor data from decoded bytes", e);
+ }
+
+ // Consume tag
+ XmlReader.readEndTag(parser);
+
+ return new SerializedVendorEffect(vendorData);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
index 2b8b61d..a9fbcaf 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -18,13 +18,15 @@
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
import android.annotation.NonNull;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.modules.utils.TypedXmlPullParser;
import java.io.IOException;
@@ -80,6 +82,16 @@
* }
* </pre>
*
+ * * Vendor vibration effects
+ *
+ * <pre>
+ * {@code
+ * <vibration-effect>
+ * <vendor-effect>base64-representation-of-persistable-bundle</vendor-effect>
+ * </vibration-effect>
+ * }
+ * </pre>
+ *
* @hide
*/
public class VibrationEffectXmlParser {
@@ -87,11 +99,9 @@
/**
* Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}
* wrapping a {@link VibrationEffect}.
- *
- * @see XmlParser#parseTag(TypedXmlPullParser)
*/
@NonNull
- public static XmlSerializedVibration<VibrationEffect> parseTag(
+ public static XmlSerializedVibration<? extends VibrationEffect> parseTag(
@NonNull TypedXmlPullParser parser, @XmlConstants.Flags int flags)
throws XmlParserException, IOException {
XmlValidator.checkStartTag(parser, TAG_VIBRATION_EFFECT);
@@ -107,8 +117,9 @@
* <p>This can be reused for reading a vibration from an XML root tag or from within a combined
* vibration, but it should always be called from places that validates the top level tag.
*/
- static SerializedVibrationEffect parseVibrationContent(TypedXmlPullParser parser,
- @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+ private static XmlSerializedVibration<? extends VibrationEffect> parseVibrationContent(
+ TypedXmlPullParser parser, @XmlConstants.Flags int flags)
+ throws XmlParserException, IOException {
String vibrationTagName = parser.getName();
int vibrationTagDepth = parser.getDepth();
@@ -116,11 +127,16 @@
XmlReader.readNextTagWithin(parser, vibrationTagDepth),
"Unsupported empty vibration tag");
- SerializedVibrationEffect serializedVibration;
+ XmlSerializedVibration<? extends VibrationEffect> serializedVibration;
switch (parser.getName()) {
+ case TAG_VENDOR_EFFECT:
+ if (Flags.vendorVibrationEffects()) {
+ serializedVibration = SerializedVendorEffect.Parser.parseNext(parser, flags);
+ break;
+ } // else fall through
case TAG_PREDEFINED_EFFECT:
- serializedVibration = new SerializedVibrationEffect(
+ serializedVibration = new SerializedComposedEffect(
SerializedPredefinedEffect.Parser.parseNext(parser, flags));
break;
case TAG_PRIMITIVE_EFFECT:
@@ -128,11 +144,11 @@
do { // First primitive tag already open
primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser));
} while (XmlReader.readNextTagWithin(parser, vibrationTagDepth));
- serializedVibration = new SerializedVibrationEffect(
+ serializedVibration = new SerializedComposedEffect(
primitives.toArray(new SerializedSegment[primitives.size()]));
break;
case TAG_WAVEFORM_EFFECT:
- serializedVibration = new SerializedVibrationEffect(
+ serializedVibration = new SerializedComposedEffect(
SerializedAmplitudeStepWaveform.Parser.parseNext(parser));
break;
default:
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
index f561c14..d74a23d 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
@@ -17,13 +17,15 @@
package com.android.internal.vibrator.persistence;
import android.annotation.NonNull;
+import android.os.PersistableBundle;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import com.android.internal.vibrator.persistence.SerializedVibrationEffect.SerializedSegment;
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
import com.android.internal.vibrator.persistence.XmlConstants.PredefinedEffectName;
import com.android.internal.vibrator.persistence.XmlConstants.PrimitiveEffectName;
@@ -41,6 +43,7 @@
* <li>{@link VibrationEffect#createWaveform(long[], int[], int)}
* <li>A composition created exclusively via
* {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
+ * <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)}
* </ul>
*
* @hide
@@ -49,13 +52,16 @@
/**
* Creates a serialized representation of the input {@code vibration}.
- *
- * @see XmlSerializer#serialize
*/
@NonNull
- public static XmlSerializedVibration<VibrationEffect> serialize(
+ public static XmlSerializedVibration<? extends VibrationEffect> serialize(
@NonNull VibrationEffect vibration, @XmlConstants.Flags int flags)
throws XmlSerializerException {
+ if (Flags.vendorVibrationEffects()
+ && (vibration instanceof VibrationEffect.VendorEffect vendorEffect)) {
+ return serializeVendorEffect(vendorEffect);
+ }
+
XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed,
"Unsupported VibrationEffect type %s", vibration);
@@ -73,7 +79,7 @@
return serializeWaveformEffect(composed);
}
- private static SerializedVibrationEffect serializePredefinedEffect(
+ private static SerializedComposedEffect serializePredefinedEffect(
VibrationEffect.Composed effect, @XmlConstants.Flags int flags)
throws XmlSerializerException {
List<VibrationEffectSegment> segments = effect.getSegments();
@@ -81,10 +87,15 @@
"Unsupported repeating predefined effect %s", effect);
XmlValidator.checkSerializerCondition(segments.size() == 1,
"Unsupported multiple segments in predefined effect %s", effect);
- return new SerializedVibrationEffect(serializePrebakedSegment(segments.get(0), flags));
+ return new SerializedComposedEffect(serializePrebakedSegment(segments.get(0), flags));
}
- private static SerializedVibrationEffect serializePrimitiveEffect(
+ private static SerializedVendorEffect serializeVendorEffect(
+ VibrationEffect.VendorEffect effect) {
+ return new SerializedVendorEffect(effect.getVendorData());
+ }
+
+ private static SerializedComposedEffect serializePrimitiveEffect(
VibrationEffect.Composed effect) throws XmlSerializerException {
List<VibrationEffectSegment> segments = effect.getSegments();
XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1,
@@ -95,10 +106,10 @@
primitives[i] = serializePrimitiveSegment(segments.get(i));
}
- return new SerializedVibrationEffect(primitives);
+ return new SerializedComposedEffect(primitives);
}
- private static SerializedVibrationEffect serializeWaveformEffect(
+ private static SerializedComposedEffect serializeWaveformEffect(
VibrationEffect.Composed effect) throws XmlSerializerException {
SerializedAmplitudeStepWaveform.Builder serializedWaveformBuilder =
new SerializedAmplitudeStepWaveform.Builder();
@@ -120,7 +131,7 @@
segment.getDuration(), toAmplitudeInt(segment.getAmplitude()));
}
- return new SerializedVibrationEffect(serializedWaveformBuilder.build());
+ return new SerializedComposedEffect(serializedWaveformBuilder.build());
}
private static SerializedPredefinedEffect serializePrebakedSegment(
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index 8b92153..2a55d99 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -40,6 +40,7 @@
public static final String TAG_PREDEFINED_EFFECT = "predefined-effect";
public static final String TAG_PRIMITIVE_EFFECT = "primitive-effect";
+ public static final String TAG_VENDOR_EFFECT = "vendor-effect";
public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
public static final String TAG_REPEATING = "repeating";
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParser.java b/core/java/com/android/internal/vibrator/persistence/XmlParser.java
deleted file mode 100644
index 6712f1c..0000000
--- a/core/java/com/android/internal/vibrator/persistence/XmlParser.java
+++ /dev/null
@@ -1,51 +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.internal.vibrator.persistence;
-
-import android.annotation.NonNull;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import java.io.IOException;
-
-/**
- * Parse XML tags into valid {@link XmlSerializedVibration} instances.
- *
- * @param <T> The vibration type that will be parsed.
- * @see XmlSerializedVibration
- * @hide
- */
-@FunctionalInterface
-public interface XmlParser<T> {
-
- /**
- * Parses the current XML tag with all nested tags into a single {@link XmlSerializedVibration}.
- *
- * <p>This method will consume nested XML tags until it finds the
- * {@link TypedXmlPullParser#END_TAG} for the current tag.
- *
- * <p>The vibration reconstructed by the returned {@link XmlSerializedVibration#deserialize()}
- * is guaranteed to be valid. This method will throw an exception otherwise.
- *
- * @param pullParser The {@link TypedXmlPullParser} with the input XML.
- * @return The parsed vibration wrapped in a {@link XmlSerializedVibration} representation.
- * @throws IOException On any I/O error while reading the input XML
- * @throws XmlParserException If the XML content does not represent a valid vibration.
- */
- XmlSerializedVibration<T> parseTag(@NonNull TypedXmlPullParser pullParser)
- throws XmlParserException, IOException;
-}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
index 7507864..e2b30e7 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlParserException.java
@@ -23,7 +23,6 @@
/**
* Represents an error while parsing a vibration XML input.
*
- * @see XmlParser
* @hide
*/
public final class XmlParserException extends Exception {
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlReader.java b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
index a5ace84..0ac6fef 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlReader.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlReader.java
@@ -130,6 +130,25 @@
}
/**
+ * Read the next element, ignoring comments and ignorable whitespace, and returns only if it's a
+ * {@link XmlPullParser#TEXT}. Any other tag will fail this check.
+ *
+ * <p>The parser will be pointing to the first next element after skipping comments,
+ * instructions and ignorable whitespace.
+ */
+ public static void readNextText(TypedXmlPullParser parser, String tagName)
+ throws XmlParserException, IOException {
+ try {
+ int type = parser.next(); // skips comments, instruction tokens and ignorable whitespace
+ XmlValidator.checkParserCondition(type == XmlPullParser.TEXT,
+ "Unexpected event %s of type %d, expected text event inside tag %s",
+ parser.getName(), type, tagName);
+ } catch (XmlPullParserException e) {
+ throw XmlParserException.createFromPullParserException("text event", e);
+ }
+ }
+
+ /**
* Check parser has a {@link XmlPullParser#END_TAG} as the next tag, with no nested tags.
*
* <p>The parser will be pointing to the end tag after this method.
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
index 3233fa2..c20b7d2 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializedVibration.java
@@ -26,8 +26,7 @@
* Serialized representation of a generic vibration.
*
* <p>This can be used to represent a {@link android.os.CombinedVibration} or a
- * {@link android.os.VibrationEffect}. Instances can be created from vibration objects via
- * {@link XmlSerializer}, or from XML content via {@link XmlParser}.
+ * {@link android.os.VibrationEffect}.
*
* <p>The separation of serialization and writing procedures enables configurable rules to define
* which vibrations can be successfully serialized before any data is written to the output stream.
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java
deleted file mode 100644
index 102e6c1..0000000
--- a/core/java/com/android/internal/vibrator/persistence/XmlSerializer.java
+++ /dev/null
@@ -1,40 +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.internal.vibrator.persistence;
-
-import android.annotation.NonNull;
-
-/**
- * Creates a {@link XmlSerializedVibration} instance representing a vibration.
- *
- * @param <T> The vibration type that will be serialized.
- * @see XmlSerializedVibration
- * @hide
- */
-@FunctionalInterface
-public interface XmlSerializer<T> {
-
- /**
- * Creates a serialized representation of the input {@code vibration}.
- *
- * @param vibration The vibration to be serialized
- * @return The serialized representation of the input vibration
- * @throws XmlSerializerException If the input vibration cannot be serialized
- */
- @NonNull
- XmlSerializedVibration<T> serialize(@NonNull T vibration) throws XmlSerializerException;
-}
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
index c57ff5d..2e7ad09 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlSerializerException.java
@@ -19,7 +19,6 @@
/**
* Represents an error while serializing a vibration input.
*
- * @see XmlSerializer
* @hide
*/
public final class XmlSerializerException extends Exception {
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
index 84d4f3f..1b5a356 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlValidator.java
@@ -18,7 +18,7 @@
import static java.util.Objects.requireNonNull;
-import android.annotation.NonNull;
+import android.os.VibrationEffect;
import android.text.TextUtils;
import com.android.internal.util.ArrayUtils;
@@ -82,11 +82,11 @@
* Check given {@link XmlSerializedVibration} represents the expected {@code vibration} object
* when it's deserialized.
*/
- @NonNull
- public static <T> void checkSerializedVibration(
- XmlSerializedVibration<T> serializedVibration, T expectedVibration)
+ public static void checkSerializedVibration(
+ XmlSerializedVibration<? extends VibrationEffect> serializedVibration,
+ VibrationEffect expectedVibration)
throws XmlSerializerException {
- T deserializedVibration = requireNonNull(serializedVibration.deserialize());
+ VibrationEffect deserializedVibration = requireNonNull(serializedVibration.deserialize());
checkSerializerCondition(Objects.equals(expectedVibration, deserializedVibration),
"Unexpected serialized vibration %s: found deserialization %s, expected %s",
serializedVibration, deserializedVibration, expectedVibration);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index ad90a63..a59ee77 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -105,7 +105,10 @@
}
default void removeMessage(ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) {
- getGroup().removeMessage(this, toRecycle);
+ final MessagingGroup group = getGroup();
+ if (group != null) {
+ group.removeMessage(this, toRecycle);
+ }
}
default void setMessagingGroup(MessagingGroup group) {
@@ -132,7 +135,12 @@
@Override
default void hideAnimated() {
setIsHidingAnimated(true);
- getGroup().performRemoveAnimation(getView(), () -> setIsHidingAnimated(false));
+ final MessagingGroup group = getGroup();
+ if (group != null) {
+ group.performRemoveAnimation(getView(), () -> setIsHidingAnimated(false));
+ } else {
+ setIsHidingAnimated(false);
+ }
}
default boolean hasOverlappingRendering() {
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index 37d1c5b..5892396 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -89,6 +89,205 @@
bytes adaptive_bitmap = 8;
};
}
+
+ /**
+ * Represents a CharSequence with Spans.
+ */
+ message CharSequence {
+ optional string text = 1;
+ repeated Span spans = 2;
+
+ message Span {
+ optional int32 start = 1;
+ optional int32 end = 2;
+ optional int32 flags = 3;
+ // We use `repeated` for the following fields so that ProtoOutputStream does not omit
+ // empty messages (e.g. EasyEdit, Superscript). In practice, only one of the following
+ // fields will be written per Span message. We cannot use `oneof` here because
+ // ProtoOutputStream will omit empty messages.
+ repeated AbsoluteSize absolute_size = 4;
+ repeated AccessibilityClickable accessibility_clickable = 5;
+ repeated AccessibilityReplacement accessibility_replacement = 6;
+ repeated AccessibilityUrl accessibility_url = 7;
+ repeated Alignment alignment = 8;
+ repeated Annotation annotation = 9;
+ repeated BackgroundColor background_color = 10;
+ repeated Bullet bullet = 11;
+ repeated EasyEdit easy_edit = 12;
+ repeated ForegroundColor foreground_color = 13;
+ repeated LeadingMargin leading_margin = 14;
+ repeated LineBackground line_background = 15;
+ repeated LineBreak line_break = 16;
+ repeated LineHeight line_height = 17;
+ repeated Locale locale = 18;
+ repeated Quote quote = 19;
+ repeated RelativeSize relative_size = 20;
+ repeated ScaleX scale_x = 21;
+ repeated SpellCheck spell_check = 22;
+ repeated Strikethrough strikethrough = 23;
+ repeated Style style = 24;
+ repeated Subscript subscript = 25;
+ repeated Suggestion suggestion = 26;
+ repeated SuggestionRange suggestion_range = 27;
+ repeated Superscript superscript = 28;
+ repeated TextAppearance text_appearance = 29;
+ repeated Tts tts = 30;
+ repeated Typeface typeface = 31;
+ repeated Underline underline = 32;
+ repeated Url url = 33;
+
+ message AbsoluteSize {
+ optional int32 size = 1;
+ optional bool dip = 2;
+ }
+
+ message AccessibilityClickable {
+ optional int32 original_clickable_span_id = 1;
+ }
+
+ message AccessibilityReplacement {
+ optional CharSequence content_description = 1;
+ }
+
+ message AccessibilityUrl {
+ optional string url = 1;
+ }
+
+ message Alignment {
+ optional string alignment = 1;
+ }
+
+ message Annotation {
+ optional string key = 1;
+ optional string value = 2;
+ }
+
+ message BackgroundColor {
+ optional int32 color = 1;
+ }
+
+ message Bullet {
+ optional int32 gap_width = 1;
+ optional int32 color = 2;
+ optional int32 bullet_radius = 3;
+ optional bool want_color = 4;
+ }
+
+ message EasyEdit {}
+
+ message ForegroundColor {
+ optional int32 color = 1;
+ }
+
+ message LeadingMargin {
+ optional int32 first = 1;
+ optional int32 rest = 2;
+ }
+
+ message LineBackground {
+ optional int32 color = 1;
+ }
+
+ message LineBreak {
+ optional int32 line_break_style = 1;
+ optional int32 line_break_word_style = 2;
+ optional int32 hyphenation = 3;
+ }
+
+ message LineHeight {
+ optional int32 height = 1;
+ }
+
+ message Locale {
+ optional string language_tags = 1;
+ }
+
+ message Quote {
+ optional int32 color = 1;
+ optional int32 stripe_width = 2;
+ optional int32 gap_width = 3;
+ }
+
+ message RelativeSize {
+ optional float proportion = 1;
+ }
+
+ message ScaleX {
+ optional float proportion = 1;
+ }
+
+ message SpellCheck {
+ optional bool in_progress = 1;
+ }
+
+ message Strikethrough {}
+
+ message Style {
+ optional int32 style = 1;
+ optional int32 font_weight_adjustment = 2;
+ }
+
+ message Subscript {}
+
+ message Suggestion {
+ repeated string suggestions = 1;
+ optional int32 flags = 2;
+ optional string locale_string_for_compatibility = 3;
+ optional string language_tag = 4;
+ optional int32 hash_code = 5;
+ optional int32 easy_correct_underline_color = 6;
+ optional float easy_correct_underline_thickness = 7;
+ optional int32 misspelled_underline_color = 8;
+ optional float misspelled_underline_thickness = 9;
+ optional int32 auto_correction_underline_color = 10;
+ optional float auto_correction_underline_thickness = 11;
+ optional int32 grammar_error_underline_color = 12;
+ optional float grammar_error_underline_thickness = 13;
+ }
+
+ message SuggestionRange {
+ optional int32 background_color = 1;
+ }
+
+ message Superscript {}
+
+ // Typeface is omitted
+ message TextAppearance {
+ optional string family_name = 1;
+ optional int32 style = 2;
+ optional int32 text_size = 3;
+ optional android.content.res.ColorStateListProto text_color = 4;
+ optional android.content.res.ColorStateListProto text_color_link = 5;
+ optional int32 text_font_weight = 7;
+ optional string text_locale = 8;
+ optional float shadow_radius = 9;
+ optional float shadow_dx = 10;
+ optional float shadow_dy = 11;
+ optional int32 shadow_color = 12;
+ optional bool has_elegant_text_height_field = 13;
+ optional bool elegant_text_height = 14;
+ optional bool has_letter_spacing_field = 15;
+ optional float letter_spacing = 16;
+ optional string font_feature_settings = 17;
+ optional string font_variation_settings = 18;
+ }
+
+ message Tts {
+ optional string type = 1;
+ optional string args = 2;
+ }
+
+ message Typeface {
+ optional string family = 1;
+ }
+
+ message Underline {}
+
+ message Url {
+ optional string url = 1;
+ }
+ }
+ }
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 50727a2..7aeabee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8132,6 +8132,12 @@
<permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
android:protectionLevel="signature" />
+ <!-- Allows low-level access to monitor keyboard system shortcuts
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS"
+ android:protectionLevel="signature" />
+
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
<!-- Allows financed device kiosk apps to perform actions on the Device Lock service
diff --git a/core/res/res/layout/input_method_switch_dialog_new.xml b/core/res/res/layout/input_method_switch_dialog_new.xml
index 5a4d6b1..610a212 100644
--- a/core/res/res/layout/input_method_switch_dialog_new.xml
+++ b/core/res/res/layout/input_method_switch_dialog_new.xml
@@ -17,25 +17,35 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
- <com.android.internal.widget.MaxHeightFrameLayout
- android:layout_width="320dp"
+ <LinearLayout
+ android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
- android:maxHeight="373dp">
+ android:orientation="horizontal">
- <com.android.internal.widget.RecyclerView
- android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingVertical="8dp"
- android:clipToPadding="false"
- android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+ <!-- TODO(b/357644229): Enable shrinking width without three levels of nesting. -->
+ <com.android.internal.widget.MaxHeightFrameLayout
+ android:layout_width="320dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:maxHeight="373dp">
- </com.android.internal.widget.MaxHeightFrameLayout>
+ <com.android.internal.widget.RecyclerView
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingVertical="8dp"
+ android:clipToPadding="false"
+ android:layoutManager="com.android.internal.widget.LinearLayoutManager"/>
+
+ </com.android.internal.widget.MaxHeightFrameLayout>
+
+ </LinearLayout>
<LinearLayout
style="?android:attr/buttonBarStyle"
@@ -51,7 +61,8 @@
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_weight="1"/>
+ android:layout_weight="1"
+ android:importantForAccessibility="no"/>
<Button
style="?attr/buttonBarButtonStyle"
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index 16a97c4..09ed650 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -31,7 +31,8 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
- android:visibility="gone"/>
+ android:visibility="gone"
+ android:importantForAccessibility="no"/>
<TextView
android:id="@+id/header_text"
@@ -81,7 +82,8 @@
android:layout_marginStart="12dp"
android:src="@drawable/ic_check_24dp"
android:tint="?attr/materialColorOnSurface"
- android:visibility="gone"/>
+ android:visibility="gone"
+ android:importantForAccessibility="no"/>
</LinearLayout>
diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml
index a30be6a..5854e81 100644
--- a/core/res/res/layout/list_menu_item_icon.xml
+++ b/core/res/res/layout/list_menu_item_icon.xml
@@ -18,6 +18,8 @@
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:maxWidth="@dimen/list_menu_item_icon_max_width"
+ android:adjustViewBounds="true"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dip"
android:layout_marginEnd="-8dip"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 77b5587..f397ef2 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -1065,4 +1065,7 @@
<!-- The non-linear progress interval when the screen is wider than the
navigation_edge_action_progress_threshold. -->
<item name="back_progress_non_linear_factor" format="float" type="dimen">0.2</item>
+
+ <!-- The maximum width for a context menu icon -->
+ <dimen name="list_menu_item_icon_max_width">24dp</dimen>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9104379..cb58339 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3277,6 +3277,9 @@
<string name="input_method_nav_back_button_desc">Back</string>
<!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="input_method_ime_switch_button_desc">Switch input method</string>
+ <!-- Accessibility text for the long click action on the switch input method button. This will
+ be used following "Double-tap and hold to..." [CHAR LIMIT=NONE] -->
+ <string name="input_method_ime_switch_long_click_action_desc">Open input method picker</string>
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. -->
<string name="low_internal_storage_view_title">Storage space running out</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8f4018f..0d16e9c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2245,6 +2245,7 @@
<java-symbol type="string" name="heavy_weight_notification_detail" />
<java-symbol type="string" name="image_wallpaper_component" />
<java-symbol type="string" name="input_method_binding_label" />
+ <java-symbol type="string" name="input_method_ime_switch_long_click_action_desc" />
<java-symbol type="string" name="launch_warning_original" />
<java-symbol type="string" name="launch_warning_replace" />
<java-symbol type="string" name="launch_warning_title" />
@@ -5461,7 +5462,9 @@
<java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" />
<java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" />
+ <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
+ <java-symbol type="xml" name="haptic_feedback_customization" />
<!-- For ActivityManager PSS profiling configurability -->
<java-symbol type="bool" name="config_am_disablePssProfiling" />
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/core/res/res/xml/haptic_feedback_customization.xml
similarity index 68%
copy from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
copy to core/res/res/xml/haptic_feedback_customization.xml
index cd7a879..7ac0787 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/core/res/res/xml/haptic_feedback_customization.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2024 The Android Open Source Project
~
@@ -14,9 +15,4 @@
~ limitations under the License.
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.overlay.hsumconfig"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
-</manifest>
+<haptic-feedback-constants/>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 5793bbe..2bbaf9c 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -249,6 +249,7 @@
],
srcs: [
"src/android/app/ActivityManagerTest.java",
+ "src/android/colormodel/CamTest.java",
"src/android/content/ContextTest.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c05ea3d..fc3c2f3 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -265,6 +265,17 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.ChronometerActivity"
+ android:label="ChronometerActivity"
+ android:screenOrientation="portrait"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.Light">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.DatePickerActivity"
android:label="DatePickerActivity"
android:screenOrientation="portrait"
diff --git a/core/tests/coretests/res/layout/chronometer_layout.xml b/core/tests/coretests/res/layout/chronometer_layout.xml
new file mode 100644
index 0000000..f209c41
--- /dev/null
+++ b/core/tests/coretests/res/layout/chronometer_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Chronometer
+ android:id="@+id/chronometer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</FrameLayout>
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
index 78cd1e1..e7009d14 100644
--- a/core/tests/coretests/res/values/styles.xml
+++ b/core/tests/coretests/res/values/styles.xml
@@ -61,4 +61,28 @@
<style name="IsFrameRatePowerSavingsBalancedEnabled">
<item name="android:windowIsFrameRatePowerSavingsBalanced">true</item>
</style>
+ <style name="customFont">
+ <item name="android:fontFamily">@font/samplefont</item>
+ </style>
+ <style name="customFontWithStyle">
+ <item name="android:fontFamily">@font/samplefont</item>
+ <item name="android:textStyle">bold|italic</item>
+ </style>
+ <style name="textAppearanceWithAllAttributes">
+ <item name="android:fontFamily">@font/samplefont</item>
+ <item name="android:textStyle">bold|italic</item>
+ <item name="android:textSize">160dp</item>
+ <item name="android:textColor">#FF00FF</item>
+ <item name="android:textColorLink">#00FFFF</item>
+ <item name="android:textLocale">ja-JP,zh-CN</item>
+ <item name="android:shadowColor">#00FFFF</item>
+ <item name="android:shadowDx">1.0</item>
+ <item name="android:shadowDy">2.0</item>
+ <item name="android:shadowRadius">3.0</item>
+ <item name="android:elegantTextHeight">true</item>
+ <item name="android:letterSpacing">1.0</item>
+ <item name="android:fontFeatureSettings">\"smcp\"</item>
+ <item name="android:fontVariationSettings">\'wdth\' 150</item>
+ </style>
+
</resources>
diff --git a/core/tests/coretests/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java
index 05fc0e0..cf398db 100644
--- a/core/tests/coretests/src/android/colormodel/CamTest.java
+++ b/core/tests/coretests/src/android/colormodel/CamTest.java
@@ -18,9 +18,12 @@
import static org.junit.Assert.assertEquals;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.filters.LargeTest;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -35,6 +38,9 @@
static final int GREEN = 0xff00ff00;
static final int BLUE = 0xff0000ff;
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
@Test
public void camFromIntToInt() {
Cam cam = Cam.fromInt(RED);
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
new file mode 100644
index 0000000..8a54e5b
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * PaintTest tests {@link Paint}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PaintFontVariationTest extends InstrumentationTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ @Test
+ public void testDerivedFromSameTypeface() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ @Test
+ public void testDerivedFromChained() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
+}
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 0dec756..878ba70 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -16,13 +16,22 @@
package android.graphics;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertNotEquals;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
import androidx.test.filters.SmallTest;
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+
import java.util.Arrays;
import java.util.HashSet;
@@ -30,6 +39,9 @@
* PaintTest tests {@link Paint}.
*/
public class PaintTest extends InstrumentationTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
static void assertEquals(String message, float[] expected, float[] actual) {
@@ -403,4 +415,33 @@
assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
}
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ public void testDerivedFromSameTypeface() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+ public void testDerivedFromChained() {
+ final Paint p = new Paint();
+
+ p.setTypeface(Typeface.SANS_SERIF);
+ assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+ Typeface first = p.getTypeface();
+
+ assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+ Typeface second = p.getTypeface();
+
+ assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+ }
}
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 2f336ab..e2c1902 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -20,6 +20,10 @@
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+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.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -30,7 +34,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.text.flags.Flags;
+
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,6 +48,9 @@
private static View sView;
private static final String TEXT = "abc def";
+ @Rule
+ public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@BeforeClass
public static void setupClass() {
final Context context = InstrumentationRegistry.getTargetContext();
@@ -76,11 +86,13 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
public void transformedText_charAt_editing() {
transformedText_charAt_editing(false, "\n\n");
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
public void transformedText_charAt_singleLine_editing() {
transformedText_charAt_editing(true, "\uFFFD");
}
@@ -132,6 +144,64 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_charAt_editing_stickyHighlightRange() {
+ transformedText_charAt_editing_stickyHighlightRange(false, "\n\n");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_charAt_singleLine_editing_stickyHighlightRange() {
+ transformedText_charAt_editing_stickyHighlightRange(true, "\uFFFD");
+ }
+
+ private void transformedText_charAt_editing_stickyHighlightRange(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+ // original text is "abcxx def" after insertion.
+ text.insert(3, "xx");
+ assertCharSequence(transformedText, "abcxx" + placeholder + " def");
+
+ // original text is "abcxx vvdef" after insertion.
+ text.insert(6, "vv");
+ assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");
+
+ // original text is "abc vvdef" after deletion.
+ text.delete(3, 5);
+ assertCharSequence(transformedText, "abc" + placeholder + " vvdef");
+
+ // original text is "abc def" after deletion.
+ text.delete(4, 6);
+ assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+ // original text is "abdef" after deletion.
+ // deletion range covers the placeholder's insertion point. It'll try to stay the same,
+ // which is still at index 3.
+ text.delete(2, 4);
+ assertCharSequence(transformedText, "abd" + placeholder + "ef");
+
+ // original text is "axxdef" after replace.
+ // this time the replaced range is ahead of the placeholder's insertion point. It updates to
+ // index 4.
+ text.replace(1, 2, "xx");
+ assertCharSequence(transformedText, "axxd" + placeholder + "ef");
+
+ // original text is "ax" after replace.
+ // the deleted range covers the placeholder's insertion point. It tries to stay at index 4.
+ // However, 4 out of bounds now. So placeholder is inserted at the end of the string.
+ text.delete(2, 6);
+ assertCharSequence(transformedText, "ax" + placeholder);
+ }
+
+ @Test
public void transformedText_subSequence() {
for (int offset = 0; offset < TEXT.length(); ++offset) {
final InsertModeTransformationMethod transformationMethod =
@@ -697,7 +767,7 @@
}
@Test
- public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
+ public void transformedText_getHighlightStartAndEnd_singleLine_insertion() {
transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
}
@@ -751,16 +821,18 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
public void transformedText_getHighlightStartAndEnd_deletion() {
transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
}
@Test
- public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
+ @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_getHighlightStartAndEnd_singleLine_deletion() {
transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
}
- public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
+ private void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
String placeholder) {
final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
final InsertModeTransformationMethod transformationMethod =
@@ -816,14 +888,93 @@
assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange() {
+ transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(false, "\n\n");
+ }
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_getHighlightStartAndEnd_singleLine_deletion_stickyHighlightRange() {
+ transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(true, "\uFDDD");
+ }
+
+ private void transformedText_getHighlightStartAndEnd_deletion_stickyHighlightRange(
+ boolean singleLine, String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxxxxxx def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is still 3.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(3, "xxxxxx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abxxxxxx def" after deletion.
+ // the placeholder is now inserted at index 6.
+ // the highlight start is 2, since the deletion happens before the highlight range.
+ // the highlight end now is 8 + placeholder.length().
+ text.delete(2, 3);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());
+
+ // original text is "abxxx def" after deletion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 2, since the deletion happens in the highlight range.
+ // the highlight end now is 5 + placeholder.length().
+ text.delete(2, 5);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "abxxx d" after deletion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 2, since the deletion happens after the highlight range.
+ // the highlight end now is still 5 + placeholder.length().
+ text.delete(7, 9);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "axx d" after deletion.
+ // the placeholder is now inserted at index 3.
+ // the highlight start is at 2, since the deletion range covers the start.
+ // the highlight end is 3 + placeholder.length().
+ text.delete(1, 3);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "ax" after deletion.
+ // the placeholder is now inserted at index 2.
+ // the highlight start is at 2.
+ // the highlight end is 2 + placeholder.length(). It wants to stay at 3, but it'll be out
+ // of bounds, so it'll be 2 instead.
+ text.delete(2, 5);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(2 + placeholder.length());
+ }
+
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
public void transformedText_getHighlightStartAndEnd_replace() {
transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
}
@Test
- public void transformedText_getHighlightStartAndEnd_insertion__replace() {
+ @RequiresFlagsDisabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_getHighlightStartAndEnd_singleLine_replace() {
transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
}
@@ -908,6 +1059,99 @@
assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange() {
+ transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(false, "\n\n");
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INSERT_MODE_HIGHLIGHT_RANGE)
+ public void transformedText_getHighlightStartAndEnd_singleLine_replace_stickyHighlightRange() {
+ transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(true, "\uFDDD");
+ }
+
+ private void transformedText_getHighlightStartAndEnd_replace_stickyHighlightRange(
+ boolean singleLine, String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxxxxxx def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is still 3.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(3, "xxxxxx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abvvxxxxxx def" after replace.
+ // the replacement happens before the highlight range; highlight range is offset by 1
+ // the placeholder is now inserted at index 10,
+ // the highlight start is 4.
+ // the highlight end is 10 + placeholder.length().
+ text.replace(2, 3, "vv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());
+
+ // original text is "abvvxxx def" after replace.
+ // the replacement happens in the highlight range; highlight end is offset by -3
+ // the placeholder is now inserted at index 7,
+ // the highlight start is still 4.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(5, 9, "x");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abvvxxxvvv" after replace.
+ // the replacement happens after the highlight range; highlight is not changed
+ // the placeholder is now inserted at index 7,
+ // the highlight start is still 4.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(7, 11, "vvv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abxxxxvvv" after replace.
+ // the replacement covers the highlight start; highlight start stays the same;
+ // highlight end is offset by -1
+ // the placeholder is now inserted at index 6,
+ // the highlight start is 4.
+ // the highlight end is 6 + placeholder.length().
+ text.replace(2, 5, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+ // original text is "abxxxxxvv" after replace.
+ // the replacement covers the highlight end; highlight end stays the same;
+ // highlight start stays the same
+ // the placeholder is now inserted at index 6,
+ // the highlight start is 2.
+ // the highlight end is 6 + placeholder.length().
+ text.replace(5, 7, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+ // original text is "axxv" after replace.
+ // the replacement covers the highlight range; highlight start stays the same.
+ // highlight end shrink to the text length.
+ // the placeholder is now inserted at index 3,
+ // the highlight start is 2.
+ // the highlight end is 4 + placeholder.length().
+ text.replace(1, 8, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(4 + placeholder.length());
+ }
+
private static <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
Class<T> type) {
int currentTransition = 0;
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
index 3dfeb7f..ac6c19e 100644
--- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -16,16 +16,20 @@
package android.view;
+import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED;
import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
import static android.view.HapticFeedbackConstants.SCROLL_TICK;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.flags.FeatureFlags;
import androidx.test.InstrumentationRegistry;
@@ -33,17 +37,24 @@
import androidx.test.filters.SmallTest;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
+// TODO(b/353625893): update old tests to use new infra like those with "inputDeviceCustomized".
@SmallTest
@RunWith(AndroidJUnit4.class)
@Presubmit
public final class HapticScrollFeedbackProviderTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final int INPUT_DEVICE_1 = 1;
private static final int INPUT_DEVICE_2 = 2;
@@ -64,6 +75,7 @@
mView = new TestView(InstrumentationRegistry.getContext());
mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
/* disabledIfViewPlaysScrollHaptics= */ true);
+ mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
}
@Test
@@ -85,6 +97,26 @@
}
@Test
+ public void testRotaryEncoder_inputDeviceCustomized_noFeedbackWhenViewBasedFeedbackIsEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+ when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+ .thenReturn(true);
+ setHapticScrollTickInterval(5);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 10);
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+ }
+
+ @Test
public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() {
mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
/* disabledIfViewPlaysScrollHaptics= */ false);
@@ -107,6 +139,35 @@
}
@Test
+ public void testRotaryEncoder_inputDeviceCustomized_feedbackWhenDisregardingViewBasedScrollHaptics() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+ /* disabledIfViewPlaysScrollHaptics= */ false);
+ when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+ .thenReturn(true);
+ setHapticScrollTickInterval(5);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 10);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testNoFeedbackWhenFeedbackIsDisabled() {
setHapticScrollFeedbackEnabled(false);
// Call different types scroll feedback methods; non of them should produce feedback because
@@ -130,6 +191,31 @@
}
@Test
+ public void testNoFeedbackWhenFeedbackIsDisabled_inputDeviceCustomized() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+ setHapticScrollFeedbackEnabled(false);
+ // Call different types scroll feedback methods; non of them should produce feedback because
+ // feedback has been disabled.
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 300);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -300);
+
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+ }
+
+ @Test
public void testSnapToItem() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -138,6 +224,25 @@
}
@Test
+ public void testSnapToItem_inputDeviceCustomized() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_2, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_SCROLL);
+ requests.add(
+ new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_2, InputDevice.SOURCE_TOUCHSCREEN));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_start() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -150,6 +255,24 @@
}
@Test
+ public void testScrollLimit_inputDeviceCustomized_start() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_stop() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -162,6 +285,24 @@
}
@Test
+ public void testScrollLimit_inputDeviceCustomized_stop() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollProgress_zeroTickInterval() {
setHapticScrollTickInterval(0);
@@ -176,6 +317,22 @@
}
@Test
+ public void testScrollProgress_inputDeviceCustomized_zeroTickInterval() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+
+ setHapticScrollTickInterval(0);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 30);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 20);
+
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+ }
+
+ @Test
public void testScrollProgress_progressEqualsOrExceedsPositiveThreshold() {
setHapticScrollTickInterval(100);
mProvider.onScrollProgress(
@@ -198,6 +355,32 @@
}
@Test
+ public void testScrollProgress_inputDeviceCustomized_progressEqualsOrExceedsPositiveThreshold() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+ setHapticScrollTickInterval(100);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 20);
+
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 80);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 120);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollProgress_progressEqualsOrExceedsNegativeThreshold() {
setHapticScrollTickInterval(100);
@@ -224,6 +407,35 @@
}
@Test
+ public void testScrollProgress_inputDeviceCustomized_progressEqualsOrExceedsNegativeThreshold() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+ setHapticScrollTickInterval(100);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -20);
+
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -80);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -70);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -40);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollProgress_positiveAndNegativeProgresses() {
setHapticScrollTickInterval(100);
@@ -262,6 +474,54 @@
}
@Test
+ public void testScrollProgress_inputDeviceCustomized_positiveAndNegativeProgresses() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+ setHapticScrollTickInterval(100);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 20);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -90);
+
+ // total pixel abs = 70
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 10);
+
+ // total pixel abs = 60
+ assertThat(mView.mHapticFeedbackRequests).hasSize(0);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -50);
+ // total pixel abs = 110. Passed threshold. total pixel reduced to -10.
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 40);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 50);
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 60);
+ // total pixel abs = 140. Passed threshold. total pixel reduced to 40.
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollProgress_singleProgressExceedsThreshold() {
setHapticScrollTickInterval(100);
@@ -273,6 +533,21 @@
}
@Test
+ public void testScrollProgress_inputDeviceCustomized_singleProgressExceedsThreshold() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+ setHapticScrollTickInterval(100);
+
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 1000);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_TICK, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_startAndEndLimit_playsOnlyOneFeedback() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -288,6 +563,29 @@
}
@Test
+ public void testScrollLimit_startAndEndLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // end played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // start after end NOT played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_doubleStartLimit_playsOnlyOneFeedback() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -303,6 +601,29 @@
}
@Test
+ public void testScrollLimit_doubleStartLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // 1st start played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // 2nd start NOT played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_doubleEndLimit_playsOnlyOneFeedback() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -318,6 +639,29 @@
}
@Test
+ public void testScrollLimit_doubleEndLimit_inputDeviceCustomized_playsOnlyOneFeedback() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // 1st end played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // 2nd end NOT played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_notEnabledWithZeroProgress() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -339,6 +683,36 @@
}
@Test
+ public void testScrollLimit_inputDeviceCustomized_notEnabledWithZeroProgress() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // end played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ // progress 0. scroll not started.
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 0);
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ true);
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_enabledWithProgress() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -357,6 +731,36 @@
}
@Test
+ public void testScrollLimit_inputDeviceCustomized_enabledWithProgress() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // end played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // No tick since tick-interval is default 0, which means no tick.
+ // But still re-enable next limit feedback.
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 80);
+ // scroll pixel not 0, so end played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_enabledWithSnap() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -374,6 +778,35 @@
}
@Test
+ public void testScrollLimit_inputDeviceCustomized_enabledWithSnap() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ // 1st enabled limit by snap
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // 2nd enabled limit by snap
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_notEnabledWithDissimilarSnap() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -391,6 +824,33 @@
}
@Test
+ public void testScrollLimit_inputDeviceCustomized_notEnabledWithDissimilarSnap() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_X);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // Last snap is on AXIS_X, so end on AXIS_SCROLL is NOT played.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
public void testScrollLimit_enabledWithDissimilarProgress() {
mProvider.onSnapToItem(
INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
@@ -408,6 +868,33 @@
assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
}
+ @Test
+ public void testScrollLimit_inputDeviceCustomized_enabledWithDissimilarProgress() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ // No tick since tick-interval is default 0, which means no tick.
+ // But still re-enable next limit feedback.
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 80);
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
@Test
public void testScrollLimit_doesNotEnabledWithMotionFromDifferentDeviceId() {
@@ -428,9 +915,94 @@
assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 1);
}
+ @Test
+ public void testNonRotaryInputFeedbackNotBlockedByRotaryUnavailability() {
+ when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled())
+ .thenReturn(true);
+ setHapticScrollFeedbackEnabled(true);
+ setHapticScrollTickInterval(5);
+ mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig,
+ /* disabledIfViewPlaysScrollHaptics= */ true);
+
+ // Expect one feedback here. Touch input should provide feedback since scroll feedback has
+ // been enabled via `setHapticScrollFeedbackEnabled(true)`.
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
+ /* deltaInPixels= */ 10);
+ // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is false and
+ // `disabledIfViewPlaysScrollHaptics` is true, the scroll progress from rotary encoders will
+ // produce no feedback.
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 20);
+ // This event from the touch screen should produce feedback. The rotary encoder event's
+ // inability to not play scroll feedback should not impact this touch input.
+ mProvider.onScrollProgress(
+ INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y,
+ /* deltaInPixels= */ 30);
+
+ assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+ }
+
+ @Test
+ public void testScrollLimit_inputDeviceCustomized_doesNotEnabledWithMotionFromDifferentDeviceId() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_2, InputDevice.SOURCE_ROTARY_ENCODER));
+ // last snap was for input device #2, so limit for input device #1 not re-enabled.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
+
+ @Test
+ public void testScrollLimit_inputDeviceCustomized_doesNotEnabledWithMotionFromDifferentSource() {
+ mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED);
+ List<HapticFeedbackRequest> requests = new ArrayList<>();
+
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_LIMIT, INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER));
+ mProvider.onSnapToItem(
+ INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_SCROLL);
+ requests.add(new HapticFeedbackRequest(
+ SCROLL_ITEM_FOCUS, INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN));
+ // last snap was for input source touch screen, so rotary's limit is NOT re-enabled.
+ mProvider.onScrollLimit(
+ INPUT_DEVICE_1,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ assertThat(mView.mHapticFeedbackRequests).containsExactlyElementsIn(requests).inOrder();
+ }
private void assertNoFeedback(TestView view) {
- for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+ for (int feedback : new int[]{SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
assertFeedbackCount(view, feedback, 0);
}
}
@@ -440,7 +1012,7 @@
}
private void assertOnlyFeedback(TestView view, int expectedFeedback, int expectedCount) {
- for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+ for (int feedback : new int[]{SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
assertFeedbackCount(view, feedback, (feedback == expectedFeedback) ? expectedCount : 0);
}
}
@@ -460,8 +1032,9 @@
.thenReturn(enabled);
}
- private static class TestView extends View {
+ private static class TestView extends View {
final Map<Integer, Integer> mFeedbackCount = new HashMap<>();
+ final List<HapticFeedbackRequest> mHapticFeedbackRequests = new ArrayList<>();
TestView(Context context) {
super(context);
@@ -475,5 +1048,47 @@
mFeedbackCount.put(feedback, mFeedbackCount.get(feedback) + 1);
return true;
}
+
+ @Override
+ public void performHapticFeedbackForInputDevice(int feedback, int inputDeviceId,
+ int inputSource, int flags) {
+ mHapticFeedbackRequests.add(
+ new HapticFeedbackRequest(feedback, inputDeviceId, inputSource));
+ }
+ }
+
+ private static class HapticFeedbackRequest {
+ // <feedback, inputDeviceId, inputSource>
+ private final int[] mArgs = new int[3];
+
+ private HapticFeedbackRequest(int feedback, int inputDeviceId, int inputSource) {
+ mArgs[0] = feedback;
+ mArgs[1] = inputDeviceId;
+ mArgs[2] = inputSource;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ HapticFeedbackRequest other = (HapticFeedbackRequest) obj;
+ return Arrays.equals(this.mArgs, other.mArgs);
+ }
+
+ @Override
+ public int hashCode() {
+ // Shouldn't depend on hash. Should explicitly match mArgs.
+ return Objects.hash(mArgs[0], mArgs[1], mArgs[2]);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("<feedback=%d; inputDeviceId=%d; inputSource=%d>",
+ mArgs[0], mArgs[1], mArgs[2]);
+ }
}
}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c5b75ff..e240a08 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -18,6 +18,7 @@
import static android.util.SequenceUtils.getInitSeq;
import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -501,6 +502,20 @@
assertThat(result).isFalse();
}
+ @UiThreadTest
+ @Test
+ public void performHapticFeedbackForInputDevice_touchFeedbackDisabled_doNothing() {
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.flags = Display.FLAG_TOUCH_FEEDBACK_DISABLED;
+ Display display = new Display(DisplayManagerGlobal.getInstance(), /* displayId= */
+ 0, displayInfo, new DisplayAdjustments());
+ ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, display);
+
+ viewRootImpl.performHapticFeedbackForInputDevice(HapticFeedbackConstants.CONTEXT_CLICK,
+ 1 /* inputDeviceId */, SOURCE_ROTARY_ENCODER /* inputSource */,
+ FLAG_IGNORE_GLOBAL_SETTING, 0 /* privFlags */);
+ }
+
/**
* Test the default values are properly set
*/
@@ -1419,8 +1434,47 @@
}
@Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
+ public void votePreferredFrameRate_resetWhenDestroyingSurface()
+ throws Throwable {
+ if (!ViewProperties.vrr_enabled().orElse(true)) {
+ return;
+ }
+ mView = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(mView, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ mViewRootImpl = mView.getViewRootImpl();
+
+ waitForFrameRateCategoryToSettle(mView);
+
+ sInstrumentation.runOnMainSync(() -> {
+ mViewRootImpl.getView().setVisibility(View.INVISIBLE);
+ mViewRootImpl.mSurface.release();
+ mView.invalidate();
+ });
+ sInstrumentation.waitForIdleSync();
+
+ assertEquals(false, mViewRootImpl.mSurface.isValid());
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getPreferredFrameRateCategory());
+ assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
+ assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ }
+
+ @Test
@RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
- public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable {
+ public void votePreferredFrameRate_reset() throws Throwable {
if (!ViewProperties.vrr_enabled().orElse(true)) {
return;
}
diff --git a/core/tests/coretests/src/android/widget/ChronometerActivity.java b/core/tests/coretests/src/android/widget/ChronometerActivity.java
new file mode 100644
index 0000000..aaed430
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ChronometerActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * A minimal application for DatePickerFocusTest.
+ */
+public class ChronometerActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.chronometer_layout);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/ChronometerTest.java b/core/tests/coretests/src/android/widget/ChronometerTest.java
new file mode 100644
index 0000000..3c73837
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ChronometerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.frameworks.coretests.R;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link DatePicker} focus changes.
+ */
+@SuppressWarnings("deprecation")
+@LargeTest
+public class ChronometerTest extends ActivityInstrumentationTestCase2<ChronometerActivity> {
+
+ private Activity mActivity;
+ private Chronometer mChronometer;
+
+ public ChronometerTest() {
+ super(ChronometerActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mChronometer = mActivity.findViewById(R.id.chronometer);
+ }
+
+ public void testChronometerTicksSequentially() throws Throwable {
+ final CountDownLatch latch = new CountDownLatch(5);
+ ArrayList<String> ticks = new ArrayList<>();
+ runOnUiThread(() -> {
+ mChronometer.setOnChronometerTickListener((chronometer) -> {
+ ticks.add(chronometer.getText().toString());
+ latch.countDown();
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ });
+ mChronometer.start();
+ });
+ assertTrue(latch.await(6, TimeUnit.SECONDS));
+ assertTrue(ticks.size() >= 5);
+ assertEquals("00:00", ticks.get(0));
+ assertEquals("00:01", ticks.get(1));
+ assertEquals("00:02", ticks.get(2));
+ assertEquals("00:03", ticks.get(3));
+ assertEquals("00:04", ticks.get(4));
+ }
+
+ private void runOnUiThread(Runnable runnable) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mActivity.runOnUiThread(() -> {
+ runnable.run();
+ latch.countDown();
+ });
+ latch.await();
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
index 44d10d3..b999df4 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
+++ b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
@@ -21,17 +21,121 @@
import android.graphics.Bitmap
import android.graphics.BlendMode
import android.graphics.Color
+import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
+import android.graphics.text.LineBreakConfig
+import android.os.LocaleList
+import android.text.Layout
+import android.text.ParcelableSpan
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.TextPaint
+import android.text.style.AbsoluteSizeSpan
+import android.text.style.AccessibilityClickableSpan
+import android.text.style.AccessibilityReplacementSpan
+import android.text.style.AccessibilityURLSpan
+import android.text.style.AlignmentSpan
+import android.text.style.BackgroundColorSpan
+import android.text.style.BulletSpan
+import android.text.style.EasyEditSpan
+import android.text.style.ForegroundColorSpan
+import android.text.style.LeadingMarginSpan
+import android.text.style.LineBackgroundSpan
+import android.text.style.LineBreakConfigSpan
+import android.text.style.LineHeightSpan
+import android.text.style.LocaleSpan
+import android.text.style.QuoteSpan
+import android.text.style.RelativeSizeSpan
+import android.text.style.ScaleXSpan
+import android.text.style.SpellCheckSpan
+import android.text.style.StrikethroughSpan
+import android.text.style.StyleSpan
+import android.text.style.SubscriptSpan
+import android.text.style.SuggestionRangeSpan
+import android.text.style.SuggestionSpan
+import android.text.style.SuperscriptSpan
+import android.text.style.TextAppearanceSpan
+import android.text.style.TtsSpan
+import android.text.style.TypefaceSpan
+import android.text.style.URLSpan
+import android.text.style.UnderlineSpan
import android.util.proto.ProtoInputStream
import android.util.proto.ProtoOutputStream
+import android.widget.RemoteViewsSerializers.createAbsoluteSizeSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityClickableSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityReplacementSpanFromProto
+import android.widget.RemoteViewsSerializers.createAccessibilityURLSpanFromProto
+import android.widget.RemoteViewsSerializers.createAnnotationFromProto
+import android.widget.RemoteViewsSerializers.createBackgroundColorSpanFromProto
+import android.widget.RemoteViewsSerializers.createBulletSpanFromProto
+import android.widget.RemoteViewsSerializers.createCharSequenceFromProto
+import android.widget.RemoteViewsSerializers.createEasyEditSpanFromProto
+import android.widget.RemoteViewsSerializers.createForegroundColorSpanFromProto
import android.widget.RemoteViewsSerializers.createIconFromProto
+import android.widget.RemoteViewsSerializers.createLeadingMarginSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLineBackgroundSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLineBreakConfigSpanFromProto
+import android.widget.RemoteViewsSerializers.createLineHeightSpanStandardFromProto
+import android.widget.RemoteViewsSerializers.createLocaleSpanFromProto
+import android.widget.RemoteViewsSerializers.createQuoteSpanFromProto
+import android.widget.RemoteViewsSerializers.createRelativeSizeSpanFromProto
+import android.widget.RemoteViewsSerializers.createScaleXSpanFromProto
+import android.widget.RemoteViewsSerializers.createStrikethroughSpanFromProto
+import android.widget.RemoteViewsSerializers.createStyleSpanFromProto
+import android.widget.RemoteViewsSerializers.createSubscriptSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuggestionRangeSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuggestionSpanFromProto
+import android.widget.RemoteViewsSerializers.createSuperscriptSpanFromProto
+import android.widget.RemoteViewsSerializers.createTextAppearanceSpanFromProto
+import android.widget.RemoteViewsSerializers.createTtsSpanFromProto
+import android.widget.RemoteViewsSerializers.createTypefaceSpanFromProto
+import android.widget.RemoteViewsSerializers.createURLSpanFromProto
+import android.widget.RemoteViewsSerializers.createUnderlineSpanFromProto
+import android.widget.RemoteViewsSerializers.writeAbsoluteSizeSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityClickableSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityReplacementSpanToProto
+import android.widget.RemoteViewsSerializers.writeAccessibilityURLSpanToProto
+import android.widget.RemoteViewsSerializers.writeAlignmentSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeAnnotationToProto
+import android.widget.RemoteViewsSerializers.writeBackgroundColorSpanToProto
+import android.widget.RemoteViewsSerializers.writeBulletSpanToProto
+import android.widget.RemoteViewsSerializers.writeCharSequenceToProto
+import android.widget.RemoteViewsSerializers.writeEasyEditSpanToProto
+import android.widget.RemoteViewsSerializers.writeForegroundColorSpanToProto
import android.widget.RemoteViewsSerializers.writeIconToProto
+import android.widget.RemoteViewsSerializers.writeLeadingMarginSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLineBackgroundSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLineBreakConfigSpanToProto
+import android.widget.RemoteViewsSerializers.writeLineHeightSpanStandardToProto
+import android.widget.RemoteViewsSerializers.writeLocaleSpanToProto
+import android.widget.RemoteViewsSerializers.writeQuoteSpanToProto
+import android.widget.RemoteViewsSerializers.writeRelativeSizeSpanToProto
+import android.widget.RemoteViewsSerializers.writeScaleXSpanToProto
+import android.widget.RemoteViewsSerializers.writeStrikethroughSpanToProto
+import android.widget.RemoteViewsSerializers.writeStyleSpanToProto
+import android.widget.RemoteViewsSerializers.writeSubscriptSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuggestionRangeSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuggestionSpanToProto
+import android.widget.RemoteViewsSerializers.writeSuperscriptSpanToProto
+import android.widget.RemoteViewsSerializers.writeTextAppearanceSpanToProto
+import android.widget.RemoteViewsSerializers.writeTtsSpanToProto
+import android.widget.RemoteViewsSerializers.writeTypefaceSpanToProto
+import android.widget.RemoteViewsSerializers.writeURLSpanToProto
+import android.widget.RemoteViewsSerializers.writeUnderlineSpanToProto
+import androidx.core.os.persistableBundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.frameworks.coretests.R
import com.google.common.truth.Truth.assertThat
import java.io.ByteArrayOutputStream
+import java.util.Locale
+import kotlin.random.Random
+import kotlin.test.assertIs
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@@ -84,6 +188,511 @@
}
}
}
+
+ @Test
+ fun testWriteToProto() {
+ // This test checks that all of the supported spans are written with their start, end and
+ // flags. Span-specific data is tested in other tests.
+ val string = "0123456789"
+ data class SpanSpec(
+ val span: ParcelableSpan,
+ val start: Int = Random.nextInt(0, string.length),
+ val end: Int = Random.nextInt(start, string.length),
+ val flags: Int = Random.nextInt(0, 256).shl(Spanned.SPAN_USER_SHIFT),
+ )
+
+ val specs = listOf(
+ AbsoluteSizeSpan(0),
+ AccessibilityClickableSpan(0),
+ AccessibilityReplacementSpan(null as String?),
+ AccessibilityURLSpan(URLSpan(null)),
+ AlignmentSpan.Standard(Layout.Alignment.ALIGN_LEFT),
+ android.text.Annotation(null, null),
+ BackgroundColorSpan(0),
+ BulletSpan(0),
+ EasyEditSpan(),
+ ForegroundColorSpan(0),
+ LeadingMarginSpan.Standard(0),
+ LineBackgroundSpan.Standard(0),
+ LineBreakConfigSpan(LineBreakConfig.NONE),
+ LineHeightSpan.Standard(1),
+ LocaleSpan(LocaleList.getDefault()),
+ QuoteSpan(),
+ RelativeSizeSpan(0f),
+ ScaleXSpan(0f),
+ SpellCheckSpan(),
+ StrikethroughSpan(),
+ StyleSpan(0),
+ SubscriptSpan(),
+ SuggestionRangeSpan(),
+ SuggestionSpan(context, arrayOf(), 0),
+ SuperscriptSpan(),
+ TextAppearanceSpan(context, android.R.style.TextAppearance),
+ TtsSpan(null, persistableBundleOf()),
+ TypefaceSpan(null),
+ UnderlineSpan(),
+ URLSpan(null),
+ ).map { SpanSpec(it) }
+
+ val original = SpannableStringBuilder(string)
+ for (spec in specs) {
+ original.setSpan(spec.span, spec.start, spec.end, spec.flags)
+ }
+
+ val out = ProtoOutputStream()
+ writeCharSequenceToProto(out, original)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createCharSequenceFromProto(input)
+
+ assertIs<Spanned>(copy)
+ for (spec in specs) {
+ val spans = copy.getSpans(spec.start, spec.end, Object::class.java)
+ android.util.Log.e("TestRunner", "Can I find $spec")
+ val span = spans.single { spec.span::class.java.name == it::class.java.name }
+ assertEquals(spec.flags, copy.getSpanFlags(span))
+ }
+ }
+
+ @Test
+ fun writeToProto_notSpanned() {
+ val string = "Hello World"
+ val out = ProtoOutputStream()
+ writeCharSequenceToProto(out, string)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createCharSequenceFromProto(input)
+ assertIs<String>(copy)
+ assertEquals(copy, string)
+ }
+
+ @Test
+ fun testAbsoluteSizeSpan() {
+ for (span in arrayOf(AbsoluteSizeSpan(0, false), AbsoluteSizeSpan(2, true))) {
+ val out = ProtoOutputStream()
+ writeAbsoluteSizeSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createAbsoluteSizeSpanFromProto(input)
+ assertEquals(span.size, copy.size)
+ assertEquals(span.dip, copy.dip)
+ }
+ }
+
+ @Test
+ fun testAccessibilityClickableSpan() {
+ for (id in 0..1) {
+ val span = AccessibilityClickableSpan(id)
+ val out = ProtoOutputStream()
+ writeAccessibilityClickableSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createAccessibilityClickableSpanFromProto(input)
+ assertEquals(span.originalClickableSpanId, copy.originalClickableSpanId)
+ }
+ }
+
+ @Test
+ fun testAccessibilityReplacementSpan() {
+ for (contentDescription in arrayOf(null, "123")) {
+ val span = AccessibilityReplacementSpan(contentDescription)
+ val out = ProtoOutputStream()
+ writeAccessibilityReplacementSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createAccessibilityReplacementSpanFromProto(input)
+ assertEquals(span.contentDescription, copy.contentDescription)
+ }
+ }
+
+ @Test
+ fun testAccessibilityURLSpan() {
+ for (url in arrayOf(null, "123")) {
+ val span = AccessibilityURLSpan(URLSpan(url))
+ val out = ProtoOutputStream()
+ writeAccessibilityURLSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createAccessibilityURLSpanFromProto(input)
+ assertEquals(span.url, copy.url)
+ }
+ }
+
+ @Test
+ fun testAlignmentSpanStandard() {
+ for (alignment in arrayOf(
+ Layout.Alignment.ALIGN_CENTER,
+ Layout.Alignment.ALIGN_LEFT,
+ Layout.Alignment.ALIGN_NORMAL,
+ Layout.Alignment.ALIGN_OPPOSITE)) {
+ val span = AlignmentSpan.Standard(alignment)
+ val out = ProtoOutputStream()
+ writeAlignmentSpanStandardToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = RemoteViewsSerializers.createAlignmentSpanStandardFromProto(input)
+ assertEquals(span.alignment, copy.alignment)
+ }
+ }
+
+ @Test
+ fun testAnnotation() {
+ for ((key, value) in arrayOf(null to null, "key" to "value")) {
+ val span = android.text.Annotation(key, value)
+ val out = ProtoOutputStream()
+ writeAnnotationToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createAnnotationFromProto(input)
+ assertEquals(span.key, copy.key)
+ assertEquals(span.value, copy.value)
+ }
+ }
+
+ @Test
+ fun testBackgroundColorSpan() {
+ for (color in intArrayOf(Color.RED, Color.MAGENTA)) {
+ val span = BackgroundColorSpan(color)
+ val out = ProtoOutputStream()
+ writeBackgroundColorSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createBackgroundColorSpanFromProto(input)
+ assertEquals(span.backgroundColor, copy.backgroundColor)
+ }
+ }
+
+ @Test
+ fun testBulletSpan() {
+ for (span in arrayOf(BulletSpan(), BulletSpan(2, Color.RED, 5))) {
+ val out = ProtoOutputStream()
+ writeBulletSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createBulletSpanFromProto(input)
+ assertEquals(span.getLeadingMargin(true), copy.getLeadingMargin(true))
+ assertEquals(span.color, copy.color)
+ assertEquals(span.color, copy.color)
+ assertEquals(span.gapWidth, copy.gapWidth)
+ }
+ }
+
+ @Test
+ fun testEasyEditSpan() {
+ val span = EasyEditSpan()
+ val out = ProtoOutputStream()
+ writeEasyEditSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ createEasyEditSpanFromProto(input)
+ }
+
+ @Test
+ fun testForegroundColorSpan() {
+ for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+ val span = ForegroundColorSpan(color)
+ val out = ProtoOutputStream()
+ writeForegroundColorSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createForegroundColorSpanFromProto(input)
+ assertEquals(span.foregroundColor.toLong(), copy.foregroundColor.toLong())
+ }
+ }
+
+ @Test
+ fun testLeadingMarginSpanStandard() {
+ for (span in arrayOf(LeadingMarginSpan.Standard(10, 20), LeadingMarginSpan.Standard(0))) {
+ val out = ProtoOutputStream()
+ writeLeadingMarginSpanStandardToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createLeadingMarginSpanStandardFromProto(input)
+ assertEquals(span.getLeadingMargin(true), copy.getLeadingMargin(true))
+ assertEquals(span.getLeadingMargin(false), copy.getLeadingMargin(false))
+ }
+ }
+
+ @Test
+ fun testLineBackgroundSpan() {
+ for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+ val span = LineBackgroundSpan.Standard(color)
+ val out = ProtoOutputStream()
+ writeLineBackgroundSpanStandardToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createLineBackgroundSpanStandardFromProto(input)
+ assertEquals(span.color, copy.color)
+ }
+ }
+
+ @Test
+ fun testLineBreakConfigSpan() {
+ val config = LineBreakConfig.Builder()
+ .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO)
+ .setHyphenation(LineBreakConfig.HYPHENATION_ENABLED)
+ .build()
+ val span = LineBreakConfigSpan(config)
+ val out = ProtoOutputStream()
+ writeLineBreakConfigSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createLineBreakConfigSpanFromProto(input).lineBreakConfig
+ assertEquals(copy.lineBreakStyle, config.lineBreakStyle)
+ assertEquals(copy.lineBreakWordStyle, config.lineBreakWordStyle)
+ assertEquals(copy.hyphenation, config.hyphenation)
+ }
+
+ @Test
+ fun testLineHeightSpanStandard() {
+ for (height in 1..2) {
+ val span = LineHeightSpan.Standard(height)
+ val out = ProtoOutputStream()
+ writeLineHeightSpanStandardToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createLineHeightSpanStandardFromProto(input)
+ assertEquals(span.height, copy.height)
+ }
+ }
+
+ @Test
+ fun testLocaleSpan() {
+ for (list in arrayOf(
+ LocaleList.getEmptyLocaleList(),
+ LocaleList.forLanguageTags("en"),
+ LocaleList.forLanguageTags("en-GB,en"),
+ )) {
+ val span = LocaleSpan(list)
+ val out = ProtoOutputStream()
+ writeLocaleSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createLocaleSpanFromProto(input)
+ assertEquals(span.locales[0], copy.locale)
+ assertEquals(span.locales, copy.locales)
+ }
+ }
+
+ @Test
+ fun testQuoteSpan() {
+ for (color in intArrayOf(0, Color.RED, Color.MAGENTA)) {
+ val span = QuoteSpan(color)
+ val out = ProtoOutputStream()
+ writeQuoteSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createQuoteSpanFromProto(input)
+ assertEquals(span.color, copy.color)
+ assertTrue(span.gapWidth > 0)
+ assertTrue(span.stripeWidth > 0)
+ }
+ }
+
+ @Test
+ fun testRelativeSizeSpan() {
+ for (size in arrayOf(0f, 1.0f)) {
+ val span = RelativeSizeSpan(size)
+ val out = ProtoOutputStream()
+ writeRelativeSizeSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createRelativeSizeSpanFromProto(input)
+ assertEquals(span.sizeChange, copy.sizeChange)
+ }
+ }
+
+ @Test
+ fun testScaleXSpan() {
+ for (scale in arrayOf(0f, 1.0f)) {
+ val span = ScaleXSpan(scale)
+ val out = ProtoOutputStream()
+ writeScaleXSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createScaleXSpanFromProto(input)
+ assertEquals(span.scaleX, copy.scaleX, 0.0f)
+ }
+ }
+
+ @Test
+ fun testStrikethroughSpan() {
+ val span = StrikethroughSpan()
+ val out = ProtoOutputStream()
+ writeStrikethroughSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ createStrikethroughSpanFromProto(input)
+ }
+
+ @Test
+ fun testStyleSpan() {
+ for (style in arrayOf(Typeface.BOLD, Typeface.NORMAL)) {
+ val span = StyleSpan(style)
+ val out = ProtoOutputStream()
+ writeStyleSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createStyleSpanFromProto(input)
+ assertEquals(span.style, copy.style)
+ }
+ }
+
+ @Test
+ fun testSubscriptSpan() {
+ val span = SubscriptSpan()
+ val out = ProtoOutputStream()
+ writeSubscriptSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ createSubscriptSpanFromProto(input)
+ }
+
+ @Test
+ fun testSuggestionSpan() {
+ val suggestions = arrayOf("suggestion1", "suggestion2")
+ val span = SuggestionSpan(
+ Locale.forLanguageTag("en"), suggestions,
+ SuggestionSpan.FLAG_AUTO_CORRECTION)
+
+ val out = ProtoOutputStream()
+ writeSuggestionSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createSuggestionSpanFromProto(input)
+ assertArrayEquals("Should (de)serialize suggestions",
+ suggestions, copy.suggestions)
+ }
+
+ @Test
+ fun testSuggestionRangeSpan() {
+ for (backgroundColor in 0..1) {
+ val span = SuggestionRangeSpan()
+ span.backgroundColor = backgroundColor
+ val out = ProtoOutputStream()
+ writeSuggestionRangeSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createSuggestionRangeSpanFromProto(input)
+ assertEquals(span.backgroundColor, copy.backgroundColor)
+ }
+ }
+
+ @Test
+ fun testSuperscriptSpan() {
+ val span = SuperscriptSpan()
+ val out = ProtoOutputStream()
+ writeSuperscriptSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ createSuperscriptSpanFromProto(input)
+ }
+
+
+ @Test
+ fun testTextAppearanceSpan_FontResource() {
+ val span = TextAppearanceSpan(context, R.style.customFont)
+ val out = ProtoOutputStream()
+ writeTextAppearanceSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createTextAppearanceSpanFromProto(input)
+ val tp = TextPaint()
+ span.updateDrawState(tp)
+ val originalSpanTextWidth = tp.measureText("a")
+ copy.updateDrawState(tp)
+ assertEquals(originalSpanTextWidth, tp.measureText("a"), 0.0f)
+ }
+
+ @Test
+ fun testTextAppearanceSpan_FontResource_WithStyle() {
+ val span = TextAppearanceSpan(context, R.style.customFontWithStyle)
+ val out = ProtoOutputStream()
+ writeTextAppearanceSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createTextAppearanceSpanFromProto(input)
+ val tp = TextPaint()
+ span.updateDrawState(tp)
+ val originalSpanTextWidth = tp.measureText("a")
+ copy.updateDrawState(tp)
+ assertEquals(originalSpanTextWidth, tp.measureText("a"), 0.0f)
+ }
+
+ @Test
+ fun testTextAppearanceSpan_WithAllAttributes() {
+ val span = TextAppearanceSpan(context, R.style.textAppearanceWithAllAttributes)
+ val out = ProtoOutputStream()
+ writeTextAppearanceSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createTextAppearanceSpanFromProto(input)
+ val originalTextColor = span.textColor
+ val copyTextColor = copy.textColor
+ val originalLinkTextColor = span.linkTextColor
+ val copyLinkTextColor = copy.linkTextColor
+ assertEquals(span.family, copy.family)
+ // ColorStateList doesn't implement equals(), so we borrow this code
+ // from ColorStateListTest.java to test correctness of parceling.
+ assertEquals(originalTextColor.isStateful, copyTextColor.isStateful)
+ assertEquals(originalTextColor.defaultColor, copyTextColor.defaultColor)
+ assertEquals(originalLinkTextColor.isStateful,
+ copyLinkTextColor.isStateful)
+ assertEquals(originalLinkTextColor.defaultColor,
+ copyLinkTextColor.defaultColor)
+ assertEquals(span.textSize.toLong(), copy.textSize.toLong())
+ assertEquals(span.textStyle.toLong(), copy.textStyle.toLong())
+ assertEquals(span.textFontWeight.toLong(), copy.textFontWeight.toLong())
+ assertEquals(span.textLocales, copy.textLocales)
+ assertEquals(span.shadowColor.toLong(), copy.shadowColor.toLong())
+ assertEquals(span.shadowDx, copy.shadowDx, 0.0f)
+ assertEquals(span.shadowDy, copy.shadowDy, 0.0f)
+ assertEquals(span.shadowRadius, copy.shadowRadius, 0.0f)
+ assertEquals(span.fontFeatureSettings, copy.fontFeatureSettings)
+ assertEquals(span.fontVariationSettings, copy.fontVariationSettings)
+ assertEquals(span.isElegantTextHeight, copy.isElegantTextHeight)
+ assertEquals(span.letterSpacing, copy.letterSpacing, 0f)
+ // typeface is omitted from TextAppearanceSpan proto
+ }
+
+ @Test
+ fun testTtsSpan() {
+ val bundle = persistableBundleOf(
+ "argument.one" to "value.one",
+ "argument.two" to "value.two",
+ "argument.three" to 3L,
+ "argument.four" to 4L,
+ )
+ val span = TtsSpan("test.type.five", bundle)
+ val out = ProtoOutputStream()
+ writeTtsSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createTtsSpanFromProto(input)
+ assertEquals("test.type.five", copy.type)
+ val args = copy.args
+ assertEquals(4, args.size())
+ assertEquals("value.one", args.getString("argument.one"))
+ assertEquals("value.two", args.getString("argument.two"))
+ assertEquals(3, args.getLong("argument.three"))
+ assertEquals(4, args.getLong("argument.four"))
+ }
+
+
+ @Test
+ fun testTtsSpan_null() {
+ val span = TtsSpan(null, null)
+ val out = ProtoOutputStream()
+ writeTtsSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createTtsSpanFromProto(input)
+ assertNull(copy.type)
+ assertNull(copy.args)
+ }
+
+ @Test
+ fun testTypefaceSpan() {
+ for (family in arrayOf(null, "monospace")) {
+ val span = TypefaceSpan(family)
+ val out = ProtoOutputStream()
+ writeTypefaceSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createTypefaceSpanFromProto(input)
+ assertEquals(span.family, copy.family)
+ }
+ }
+
+ @Test
+ fun testUnderlineSpan() {
+ val span = UnderlineSpan()
+ val out = ProtoOutputStream()
+ writeUnderlineSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ createUnderlineSpanFromProto(input)
+ }
+
+ @Test
+ fun testURLSpan() {
+ for (url in arrayOf(null, "content://url")) {
+ val span = URLSpan(url)
+ val out = ProtoOutputStream()
+ writeURLSpanToProto(out, span)
+ val input = ProtoInputStream(out.bytes)
+ val copy = createURLSpanFromProto(input)
+ assertEquals(span.url, copy.url)
+ }
+ }
}
fun equalColorStateLists(a: ColorStateList?, b: ColorStateList?): Boolean {
diff --git a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 6c8dcd3..fdc00ba 100644
--- a/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -93,7 +93,8 @@
ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
- 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
+ 0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */,
+ 0 /* uiMode */);
}
private static TaskDescription createTaskDescription(int background,
diff --git a/core/tests/coretests/src/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
index bf35ed0..2362a4c 100644
--- a/core/tests/coretests/src/com/android/internal/jank/CujTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java
@@ -35,7 +35,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -47,26 +46,30 @@
public class CujTest {
private static final String ENUM_NAME_PREFIX =
"UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
- private static final Set<String> DEPRECATED_VALUES = new HashSet<>() {
- {
- add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
- }
- };
- private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() {
- {
- put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD"));
- put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR"));
- put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH"));
- put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR"));
- put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE"));
- put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE"));
- put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE"));
- put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE"));
- put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND"));
- put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
- put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
- }
- };
+ private static final Set<String> DEPRECATED_VALUES = Set.of(
+ ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"
+ );
+ private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = Map.ofEntries(
+ Map.entry(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")),
+ Map.entry(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")),
+ Map.entry(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")),
+ Map.entry(
+ Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+ getEnumName("SHADE_HEADS_UP_DISAPPEAR")),
+ Map.entry(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")),
+ Map.entry(
+ Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ getEnumName("NOTIFICATION_SHADE_SWIPE")),
+ Map.entry(
+ Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ getEnumName("SHADE_QS_EXPAND_COLLAPSE")),
+ Map.entry(
+ Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+ getEnumName("SHADE_QS_SCROLL_SWIPE")),
+ Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")),
+ Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")),
+ Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"))
+ );
@Rule
public final Expect mExpect = Expect.create();
diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp
index dd86094..efb8437 100644
--- a/core/tests/resourceflaggingtests/Android.bp
+++ b/core/tests/resourceflaggingtests/Android.bp
@@ -22,54 +22,6 @@
default_team: "trendy_team_android_resources",
}
-genrule {
- name: "resource-flagging-test-app-resources-compile",
- tools: ["aapt2"],
- srcs: [
- "flagged_resources_res/values/bools.xml",
- ],
- out: ["values_bools.arsc.flat"],
- cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
- "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
-}
-
-genrule {
- name: "resource-flagging-test-app-resources-compile2",
- tools: ["aapt2"],
- srcs: [
- "flagged_resources_res/values/bools2.xml",
- ],
- out: ["values_bools2.arsc.flat"],
- cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
- "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
-}
-
-genrule {
- name: "resource-flagging-test-app-apk",
- tools: ["aapt2"],
- // The first input file in the list must be the manifest
- srcs: [
- "TestAppAndroidManifest.xml",
- ":resource-flagging-test-app-resources-compile",
- ":resource-flagging-test-app-resources-compile2",
- ],
- out: ["resapp.apk"],
- cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
-}
-
-java_genrule {
- name: "resource-flagging-apk-as-resource",
- srcs: [
- ":resource-flagging-test-app-apk",
- ],
- out: ["apks_as_resources.res.zip"],
- tools: ["soong_zip"],
-
- cmd: "mkdir -p $(genDir)/res/raw && " +
- "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " +
- "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
-}
-
android_test {
name: "ResourceFlaggingTests",
srcs: [
@@ -82,6 +34,6 @@
"testng",
"compatibility-device-util-axt",
],
- resource_zips: [":resource-flagging-apk-as-resource"],
+ resource_zips: [":resource-flagging-test-app-apk-as-resource"],
test_suites: ["device-tests"],
}
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index ad8542e..c1e3578 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -69,11 +69,23 @@
}
private boolean getBoolean(String name) {
- int resId = mResources.getIdentifier(name, "bool", "com.android.intenal.flaggedresources");
+ int resId = mResources.getIdentifier(
+ name,
+ "bool",
+ "com.android.intenal.flaggedresources");
assertThat(resId).isNotEqualTo(0);
return mResources.getBoolean(resId);
}
+ private String getString(String name) {
+ int resId = mResources.getIdentifier(
+ name,
+ "string",
+ "com.android.intenal.flaggedresources");
+ assertThat(resId).isNotEqualTo(0);
+ return mResources.getString(resId);
+ }
+
private String extractApkAndGetPath(int id) throws Exception {
final Resources resources = mContext.getResources();
try (InputStream is = resources.openRawResource(id)) {
diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
index fc233fb..3b9f35b 100644
--- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -161,15 +161,15 @@
}
@Test
- public void testAppendBoolean() throws Exception {
+ public void testAppendBooleanDuplicatesAllowed() throws Exception {
assertArrayEquals(new boolean[] { true },
- ArrayUtils.appendBoolean(null, true));
+ ArrayUtils.appendBooleanDuplicatesAllowed(null, true));
assertArrayEquals(new boolean[] { true },
- ArrayUtils.appendBoolean(new boolean[] { }, true));
+ ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { }, true));
assertArrayEquals(new boolean[] { true, false },
- ArrayUtils.appendBoolean(new boolean[] { true }, false));
+ ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { true }, false));
assertArrayEquals(new boolean[] { true, true },
- ArrayUtils.appendBoolean(new boolean[] { true }, true));
+ ArrayUtils.appendBooleanDuplicatesAllowed(new boolean[] { true }, true));
}
@Test
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index e9a08ae..97f1d5e 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -27,7 +27,11 @@
import android.os.Parcel;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -36,6 +40,9 @@
public class PrimitiveSegmentTest {
private static final float TOLERANCE = 1e-2f;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testCreation() {
PrimitiveSegment primitive = new PrimitiveSegment(
@@ -87,7 +94,8 @@
}
@Test
- public void testScale_fullPrimitiveScaleValue() {
+ @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withLegacyScaling_fullPrimitiveScaleValue() {
PrimitiveSegment initial = new PrimitiveSegment(
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
@@ -102,7 +110,24 @@
}
@Test
- public void testScale_halfPrimitiveScaleValue() {
+ @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withScalingV2_fullPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+ assertEquals(1f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.5f).getScale(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.8f, initial.scale(0.8f).getScale(), TOLERANCE);
+ assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withLegacyScaling_halfPrimitiveScaleValue() {
PrimitiveSegment initial = new PrimitiveSegment(
VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
@@ -117,6 +142,22 @@
}
@Test
+ @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withScalingV2_halfPrimitiveScaleValue() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 0);
+
+ assertEquals(0.5f, initial.scale(1).getScale(), TOLERANCE);
+ assertEquals(0.25f, initial.scale(0.5f).getScale(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.66f, initial.scale(1.5f).getScale(), TOLERANCE);
+ assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.4f, initial.scale(0.8f).getScale(), TOLERANCE);
+ assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
public void testScale_zeroPrimitiveScaleValue() {
PrimitiveSegment initial = new PrimitiveSegment(
VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 01013ab..bea8293 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -29,7 +29,11 @@
import android.os.Parcel;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -38,6 +42,9 @@
public class RampSegmentTest {
private static final float TOLERANCE = 1e-2f;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testCreation() {
RampSegment ramp = new RampSegment(/* startAmplitude= */ 1, /* endAmplitude= */ 0,
@@ -97,14 +104,18 @@
}
@Test
- public void testScale() {
- RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+ @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withLegacyScaling_halfAndFullAmplitudes() {
+ RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0);
- assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE);
- assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
- assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
- assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
- assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE);
assertEquals(0.34f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE);
@@ -117,17 +128,38 @@
}
@Test
- public void testScale_halfPrimitiveScaleValue() {
+ @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withScalingV2_halfAndFullAmplitudes() {
RampSegment initial = new RampSegment(0.5f, 1, 0, 0, 0);
assertEquals(0.5f, initial.scale(1).getStartAmplitude(), TOLERANCE);
- assertEquals(0.17f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.25f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.66f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
// Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(0.86f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
- assertEquals(0.47f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.4f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+
+ assertEquals(1f, initial.scale(1).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.5f).getEndAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getEndAmplitude(), TOLERANCE);
+ assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getEndAmplitude(), TOLERANCE);
// Does not restore to the exact original value because scale up is a bit offset.
- assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE);
- assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.81f, initial.scale(0.8f).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getEndAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScale_zeroAmplitude() {
+ RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+ assertEquals(0f, initial.scale(1).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE);
}
@Test
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 40776ab..411074a 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -27,7 +27,11 @@
import android.os.Parcel;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -35,6 +39,10 @@
@RunWith(JUnit4.class)
public class StepSegmentTest {
private static final float TOLERANCE = 1e-2f;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testCreation() {
StepSegment step = new StepSegment(/* amplitude= */ 1f, /* frequencyHz= */ 1f,
@@ -93,7 +101,8 @@
}
@Test
- public void testScale_fullAmplitude() {
+ @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withLegacyScaling_fullAmplitude() {
StepSegment initial = new StepSegment(1f, 0, 0);
assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
@@ -107,7 +116,23 @@
}
@Test
- public void testScale_halfAmplitude() {
+ @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withScalingV2_fullAmplitude() {
+ StepSegment initial = new StepSegment(1f, 0, 0);
+
+ assertEquals(1f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(1f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(2 / 3f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.8f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+ assertEquals(0.86f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withLegacyScaling_halfAmplitude() {
StepSegment initial = new StepSegment(0.5f, 0, 0);
assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
@@ -121,6 +146,21 @@
}
@Test
+ @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testScale_withScalingV2_halfAmplitude() {
+ StepSegment initial = new StepSegment(0.5f, 0, 0);
+
+ assertEquals(0.5f, initial.scale(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.25f, initial.scale(0.5f).getAmplitude(), TOLERANCE);
+ // The original value was not scaled up, so this only scales it down.
+ assertEquals(0.66f, initial.scale(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0.44f, initial.scale(1.5f).scale(2 / 3f).getAmplitude(), TOLERANCE);
+ // Does not restore to the exact original value because scale up is a bit offset.
+ assertEquals(0.4f, initial.scale(0.8f).getAmplitude(), TOLERANCE);
+ assertEquals(0.48f, initial.scale(0.8f).scale(1.25f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
public void testScale_zeroAmplitude() {
StepSegment initial = new StepSegment(0, 0, 0);
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index bf9a820..1cc38de 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -24,22 +24,32 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertThrows;
+import android.os.PersistableBundle;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Xml;
import com.android.modules.utils.TypedXmlPullParser;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.xmlpull.v1.XmlPullParser;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
+import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -53,6 +63,9 @@
@RunWith(JUnit4.class)
public class VibrationEffectXmlSerializationTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void isSupportedMimeType_onlySupportsVibrationXmlMimeType() {
// Single MIME type supported
@@ -422,6 +435,97 @@
}
}
+ @Test
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testVendorEffect_featureFlagEnabled_allSucceed() throws Exception {
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putInt("id", 1);
+ vendorData.putDouble("scale", 0.5);
+ vendorData.putBoolean("loop", false);
+ vendorData.putLongArray("amplitudes", new long[] { 0, 255, 128 });
+ vendorData.putString("label", "vibration");
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ vendorData.writeToStream(outputStream);
+ String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray());
+
+ VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+ String xml = "<vibration-effect><vendor-effect> " // test trailing whitespace is ignored
+ + vendorDataStr
+ + " \n </vendor-effect></vibration-effect>";
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, vendorDataStr);
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, vendorDataStr);
+ assertHiddenApisRoundTrip(effect);
+
+ // Check PersistableBundle from round-trip
+ PersistableBundle parsedVendorData =
+ ((VibrationEffect.VendorEffect) parseVibrationEffect(serialize(effect),
+ /* flags= */ 0)).getVendorData();
+ assertThat(parsedVendorData.size()).isEqualTo(vendorData.size());
+ assertThat(parsedVendorData.getInt("id")).isEqualTo(1);
+ assertThat(parsedVendorData.getDouble("scale")).isEqualTo(0.5);
+ assertThat(parsedVendorData.getBoolean("loop")).isFalse();
+ assertArrayEquals(parsedVendorData.getLongArray("amplitudes"), new long[] { 0, 255, 128 });
+ assertThat(parsedVendorData.getString("label")).isEqualTo("vibration");
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testInvalidVendorEffect_featureFlagEnabled_allFail() throws IOException {
+ String emptyTag = "<vibration-effect><vendor-effect/></vibration-effect>";
+ assertPublicApisParserFails(emptyTag);
+ assertHiddenApisParserFails(emptyTag);
+
+ String emptyStringTag =
+ "<vibration-effect><vendor-effect> \n </vendor-effect></vibration-effect>";
+ assertPublicApisParserFails(emptyStringTag);
+ assertHiddenApisParserFails(emptyStringTag);
+
+ String invalidString =
+ "<vibration-effect><vendor-effect>invalid</vendor-effect></vibration-effect>";
+ assertPublicApisParserFails(invalidString);
+ assertHiddenApisParserFails(invalidString);
+
+ String validBase64String =
+ "<vibration-effect><vendor-effect>c29tZXNh</vendor-effect></vibration-effect>";
+ assertPublicApisParserFails(validBase64String);
+ assertHiddenApisParserFails(validBase64String);
+
+ PersistableBundle emptyData = new PersistableBundle();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ emptyData.writeToStream(outputStream);
+ String emptyBundleString = "<vibration-effect><vendor-effect>"
+ + Base64.getEncoder().encodeToString(outputStream.toByteArray())
+ + "</vendor-effect></vibration-effect>";
+ assertPublicApisParserFails(emptyBundleString);
+ assertHiddenApisParserFails(emptyBundleString);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ public void testVendorEffect_featureFlagDisabled_allFail() throws Exception {
+ PersistableBundle vendorData = new PersistableBundle();
+ vendorData.putInt("id", 1);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ vendorData.writeToStream(outputStream);
+ String vendorDataStr = Base64.getEncoder().encodeToString(outputStream.toByteArray());
+ String xml = "<vibration-effect><vendor-effect>"
+ + vendorDataStr
+ + "</vendor-effect></vibration-effect>";
+ VibrationEffect vendorEffect = VibrationEffect.createVendorEffect(vendorData);
+
+ assertPublicApisParserFails(xml);
+ assertPublicApisSerializerFails(vendorEffect);
+
+ assertHiddenApisParserFails(xml);
+ assertHiddenApisSerializerFails(vendorEffect);
+ }
+
private void assertPublicApisParserFails(String xml) {
assertThrows("Expected parseVibrationEffect to fail for " + xml,
VibrationXmlParser.ParseFailedException.class,
@@ -493,6 +597,12 @@
() -> serialize(effect));
}
+ private void assertHiddenApisSerializerFails(VibrationEffect effect) {
+ assertThrows("Expected serialization to fail for " + effect,
+ VibrationXmlSerializer.SerializationFailedException.class,
+ () -> serialize(effect, VibrationXmlSerializer.FLAG_ALLOW_HIDDEN_APIS));
+ }
+
private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
String... expectedSegments) throws Exception {
assertSerializationContainsSegments(serialize(effect), expectedSegments);
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index f0e13c4..280b405 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -41,9 +41,11 @@
ctor public VibrationEffect();
method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
+ method public byte[] getVendorEffect_optional();
method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
+ method public void setVendorEffect_optional(byte[]);
method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
}
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index fcd250b..21a6fac 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -46,6 +46,9 @@
<!-- Predefined vibration effect -->
<xs:element name="predefined-effect" type="PredefinedEffect"/>
+ <!-- Vendor vibration effect -->
+ <xs:element name="vendor-effect" type="VendorEffect"/>
+
<!-- Primitive composition effect -->
<xs:sequence>
<xs:element name="primitive-effect" type="PrimitiveEffect"/>
@@ -136,6 +139,10 @@
</xs:restriction>
</xs:simpleType>
+ <xs:simpleType name="VendorEffect">
+ <xs:restriction base="xs:base64Binary"/>
+ </xs:simpleType>
+
<xs:complexType name="PrimitiveEffect">
<xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
<xs:attribute name="scale" type="PrimitiveScale"/>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index b9de691..d35d777 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -44,6 +44,9 @@
<!-- Predefined vibration effect -->
<xs:element name="predefined-effect" type="PredefinedEffect"/>
+ <!-- Vendor vibration effect -->
+ <xs:element name="vendor-effect" type="VendorEffect"/>
+
<!-- Primitive composition effect -->
<xs:sequence>
<xs:element name="primitive-effect" type="PrimitiveEffect"/>
@@ -113,6 +116,10 @@
</xs:restriction>
</xs:simpleType>
+ <xs:simpleType name="VendorEffect">
+ <xs:restriction base="xs:base64Binary"/>
+ </xs:simpleType>
+
<xs:complexType name="PrimitiveEffect">
<xs:attribute name="name" type="PrimitiveEffectName" use="required"/>
<xs:attribute name="scale" type="PrimitiveScale"/>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index fd78816..889a7785 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -56,6 +56,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -74,6 +75,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -143,6 +145,23 @@
private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
private static final Object sDynamicCacheLock = new Object();
+ private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache =
+ new LruCache<>(16);
+ private static final Object sVariableCacheLock = new Object();
+
+ /** @hide */
+ @VisibleForTesting
+ public static void clearTypefaceCachesForTestingPurpose() {
+ synchronized (sWeightCacheLock) {
+ sWeightTypefaceCache.clear();
+ }
+ synchronized (sDynamicCacheLock) {
+ sDynamicTypefaceCache.evictAll();
+ }
+ synchronized (sVariableCacheLock) {
+ sVariableCache.evictAll();
+ }
+ }
@GuardedBy("SYSTEM_FONT_MAP_LOCK")
static Typeface sDefaultTypeface;
@@ -195,6 +214,8 @@
@UnsupportedAppUsage
public final long native_instance;
+ private final Typeface mDerivedFrom;
+
private final String mSystemFontFamilyName;
private final Runnable mCleaner;
@@ -274,6 +295,18 @@
}
/**
+ * Returns the Typeface used for creating this Typeface.
+ *
+ * Maybe null if this is not derived from other Typeface.
+ * TODO(b/357707916): Make this public API.
+ * @hide
+ */
+ @VisibleForTesting
+ public final @Nullable Typeface getDerivedFrom() {
+ return mDerivedFrom;
+ }
+
+ /**
* Returns the system font family name if the typeface was created from a system font family,
* otherwise returns null.
*/
@@ -1021,9 +1054,51 @@
return typeface;
}
- /** @hide */
+ private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) {
+ // The given list can be mutated because it is allocated in Paint#setFontVariationSettings.
+ // Currently, Paint#setFontVariationSettings is the only code path reaches this method.
+ axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue));
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < axes.size(); ++i) {
+ final FontVariationAxis fva = axes.get(i);
+ sb.append(fva.getTag());
+ sb.append(fva.getStyleValue());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * TODO(b/357707916): Make this public API.
+ * @hide
+ */
public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
@NonNull List<FontVariationAxis> axes) {
+ if (Flags.typefaceCacheForVarSettings()) {
+ final Typeface target = (family == null) ? Typeface.DEFAULT : family;
+ final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom;
+
+ final String key = axesToVarKey(axes);
+
+ synchronized (sVariableCacheLock) {
+ LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
+ if (innerCache == null) {
+ // Cache up to 16 var instance per root Typeface
+ innerCache = new LruCache<>(16);
+ sVariableCache.put(base.native_instance, innerCache);
+ } else {
+ Typeface cached = innerCache.get(key);
+ if (cached != null) {
+ return cached;
+ }
+ }
+ Typeface typeface = new Typeface(
+ nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
+ base.getSystemFontFamilyName(), base);
+ innerCache.put(key, typeface);
+ return typeface;
+ }
+ }
+
final Typeface base = family == null ? Typeface.DEFAULT : family;
Typeface typeface = new Typeface(
nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
@@ -1184,11 +1259,19 @@
// don't allow clients to call this directly
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Typeface(long ni) {
- this(ni, null);
+ this(ni, null, null);
+ }
+
+
+ // don't allow clients to call this directly
+ // This is kept for robolectric.
+ private Typeface(long ni, @Nullable String systemFontFamilyName) {
+ this(ni, systemFontFamilyName, null);
}
// don't allow clients to call this directly
- private Typeface(long ni, @Nullable String systemFontFamilyName) {
+ private Typeface(long ni, @Nullable String systemFontFamilyName,
+ @Nullable Typeface derivedFrom) {
if (ni == 0) {
throw new RuntimeException("native typeface cannot be made");
}
@@ -1198,6 +1281,7 @@
mStyle = nativeGetStyle(ni);
mWeight = nativeGetWeight(ni);
mSystemFontFamilyName = systemFontFamilyName;
+ mDerivedFrom = derivedFrom;
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 7be14724..25e7107 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2725,15 +2725,19 @@
TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
@NonNull WindowContainerTransaction wct, @NonNull Bundle options,
@NonNull Intent intent, @NonNull Activity launchActivity) {
+ final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
if (isActivityFromSplit(launchActivity)) {
// We restrict to launch the overlay from split. Fallback to treat it as normal
// launch.
+ Log.w(TAG, "It's not allowed to launch overlay container with tag=" + overlayTag
+ + " from activity in Activity Embedding split."
+ + " Launching activity=" + launchActivity
+ + " Fallback to launch the activity as normal launch.");
return null;
}
final List<TaskFragmentContainer> overlayContainers =
getAllNonFinishingOverlayContainers();
- final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
final boolean associateLaunchingActivity = options
.getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index f1e7ef5..99716e7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -405,8 +405,7 @@
// Sets the dim area when the two TaskFragments are adjacent.
final boolean dimOnTask = !isStacked
- && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
- && Flags.fullscreenDimFlag();
+ && splitAttributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK;
setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask);
setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask);
@@ -646,7 +645,6 @@
container);
final boolean isFillParent = relativeBounds.isEmpty();
final boolean dimOnTask = !isFillParent
- && Flags.fullscreenDimFlag()
&& attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK;
final IBinder fragmentToken = container.getTaskFragmentToken();
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index cb422ea..2e19d52 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,5 +1,5 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com
per-file res*/*/tv_*.xml = bronger@google.com
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
index ddcd5c6..e3217811 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -16,6 +16,7 @@
-->
<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
@@ -35,7 +36,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
index 1cbd0e6..f1ecde4 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -17,6 +17,7 @@
<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -51,7 +52,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
<ImageView
@@ -61,7 +62,7 @@
android:layout_marginStart="8dp"
android:contentDescription="@null"
android:src="@drawable/ic_expand_less"
- app:tint="?android:attr/textColorPrimary" />
+ app:tint="?androidprv:attr/materialColorOnSurface" />
</LinearLayout>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index dc022b4..9027bf3 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -59,7 +60,8 @@
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
return type == TRANSIT_OPEN
|| type == TRANSIT_TO_FRONT
- || type == TRANSIT_KEYGUARD_GOING_AWAY;
+ || type == TRANSIT_KEYGUARD_GOING_AWAY
+ || type == TRANSIT_PREPARE_BACK_NAVIGATION;
}
/** @return true if the transition was triggered by closing something vs opening something */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f14f419..7275c64 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,9 +16,14 @@
package com.android.wm.shell.back;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
import static com.android.window.flags.Flags.migratePredictiveBackTransition;
@@ -31,6 +36,8 @@
import android.annotation.SuppressLint;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
@@ -837,8 +844,9 @@
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
// The next callback should be {@link #onBackAnimationFinished}.
+ final boolean migrateBackToTransition = migratePredictiveBackTransition();
if (mCurrentTracker.getTriggerBack()) {
- if (migratePredictiveBackTransition()) {
+ if (migrateBackToTransition) {
// notify core gesture is commit
if (shouldTriggerCloseTransition()) {
mBackTransitionHandler.mCloseTransitionRequested = true;
@@ -856,6 +864,10 @@
// start post animation
dispatchOnBackInvoked(mActiveCallback);
} else {
+ if (migrateBackToTransition
+ && mBackTransitionHandler.mPrepareOpenTransition != null) {
+ mBackTransitionHandler.createClosePrepareTransition();
+ }
tryDispatchOnBackCancelled(mActiveCallback);
}
}
@@ -960,6 +972,7 @@
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
mReceivedNullNavigationInfo = false;
+ mBackTransitionHandler.mLastTrigger = triggerBack;
if (mBackNavigationInfo != null) {
mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
@@ -1128,12 +1141,18 @@
Runnable mOnAnimationFinishCallback;
boolean mCloseTransitionRequested;
- boolean mOpeningRunning;
SurfaceControl.Transaction mFinishOpenTransaction;
Transitions.TransitionFinishCallback mFinishOpenTransitionCallback;
QueuedTransition mQueuedTransition = null;
+ boolean mLastTrigger;
+ // The Transition to make behindActivity become visible
+ IBinder mPrepareOpenTransition;
+ // The Transition to make behindActivity become invisible, if prepare open exist and
+ // animation is canceled, start a close prepare transition to finish the whole transition.
+ IBinder mClosePrepareTransition;
+ TransitionInfo mOpenTransitionInfo;
void onAnimationFinished() {
- if (!mCloseTransitionRequested) {
+ if (!mCloseTransitionRequested && mClosePrepareTransition == null) {
applyFinishOpenTransition();
}
if (mOnAnimationFinishCallback != null) {
@@ -1158,7 +1177,8 @@
mFinishOpenTransitionCallback.onTransitionFinished(null);
mFinishOpenTransitionCallback = null;
}
- mOpeningRunning = false;
+ mOpenTransitionInfo = null;
+ mPrepareOpenTransition = null;
}
private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
@@ -1178,21 +1198,42 @@
@NonNull Transitions.TransitionFinishCallback finishCallback) {
// Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
// need to post to ShellExecutor when called.
+ if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
+ // only consume it if this transition hasn't being processed.
+ if (mClosePrepareTransition != null) {
+ mClosePrepareTransition = null;
+ applyAndFinish(st, ft, finishCallback);
+ return true;
+ }
+ return false;
+ }
+
if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
&& !isGestureBackTransition(info)) {
return false;
}
+
+ if (shouldCancelAnimation(info)) {
+ return false;
+ }
+
if (mApps == null || mApps.length == 0) {
if (mBackNavigationInfo != null && mShellBackAnimationRegistry
.isWaitingAnimation(mBackNavigationInfo.getType())) {
// Waiting for animation? Queue update to wait for animation start.
consumeQueuedTransitionIfNeeded();
mQueuedTransition = new QueuedTransition(info, st, ft, finishCallback);
- } else {
+ return true;
+ } else if (mLastTrigger) {
// animation was done, consume directly
applyAndFinish(st, ft, finishCallback);
+ return true;
+ } else {
+ // animation was cancelled but transition haven't happen, we must handle it
+ if (mClosePrepareTransition == null && mCurrentTracker.isFinished()) {
+ createClosePrepareTransition();
+ }
}
- return true;
}
if (handlePrepareTransition(info, st, ft, finishCallback)) {
@@ -1201,12 +1242,131 @@
return handleCloseTransition(info, st, ft, finishCallback);
}
+ void createClosePrepareTransition() {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.restoreBackNavi();
+ mClosePrepareTransition = mTransitions.startTransition(
+ TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, wct, mBackTransitionHandler);
+ }
+ private void mergePendingTransitions(TransitionInfo info) {
+ if (mOpenTransitionInfo == null) {
+ return;
+ }
+ // Copy initial changes to final transition
+ final TransitionInfo init = mOpenTransitionInfo;
+ // find prepare open target
+ boolean openShowWallpaper = false;
+ ComponentName openComponent = null;
+ int tmpSize;
+ int openTaskId = INVALID_TASK_ID;
+ for (int j = init.getChanges().size() - 1; j >= 0; --j) {
+ final TransitionInfo.Change change = init.getChanges().get(j);
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ openComponent = findComponentName(change);
+ openTaskId = findTaskId(change);
+ if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
+ openShowWallpaper = true;
+ }
+ break;
+ }
+ }
+ if (openComponent == null && openTaskId == INVALID_TASK_ID) {
+ // shouldn't happen.
+ return;
+ }
+ // find first non-prepare open target
+ boolean isOpen = false;
+ tmpSize = info.getChanges().size();
+ for (int j = 0; j < tmpSize; ++j) {
+ final TransitionInfo.Change change = info.getChanges().get(j);
+ final ComponentName firstNonOpen = findComponentName(change);
+ final int firstTaskId = findTaskId(change);
+ if ((firstNonOpen != null && firstNonOpen != openComponent)
+ || (firstTaskId != INVALID_TASK_ID && firstTaskId != openTaskId)) {
+ // this is original close target, potential be close, but cannot determine from
+ // it
+ if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ isOpen = !TransitionUtil.isClosingMode(change.getMode());
+ } else {
+ isOpen = TransitionUtil.isOpeningMode(change.getMode());
+ break;
+ }
+ }
+ }
+
+ if (!isOpen) {
+ // Close transition, the transition info should be:
+ // init info(open A & wallpaper)
+ // current info(close B target)
+ // remove init info(open/change A target & wallpaper)
+ boolean moveToTop = false;
+ for (int j = info.getChanges().size() - 1; j >= 0; --j) {
+ final TransitionInfo.Change change = info.getChanges().get(j);
+ if (isSameChangeTarget(openComponent, openTaskId, change)) {
+ moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
+ info.getChanges().remove(j);
+ } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
+ || !change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ info.getChanges().remove(j);
+ }
+ }
+ tmpSize = info.getChanges().size();
+ for (int i = 0; i < tmpSize; ++i) {
+ final TransitionInfo.Change change = init.getChanges().get(i);
+ if (moveToTop) {
+ if (isSameChangeTarget(openComponent, openTaskId, change)) {
+ change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
+ }
+ }
+ info.getChanges().add(i, change);
+ }
+ } else {
+ // Open transition, the transition info should be:
+ // init info(open A & wallpaper)
+ // current info(open C target + close B target + close A & wallpaper)
+
+ // If close target isn't back navigated, filter out close A & wallpaper because the
+ // (open C + close B) pair didn't participant prepare close
+ boolean nonBackOpen = false;
+ boolean nonBackClose = false;
+ tmpSize = info.getChanges().size();
+ for (int j = 0; j < tmpSize; ++j) {
+ final TransitionInfo.Change change = info.getChanges().get(j);
+ if (!change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+ && canBeTransitionTarget(change)) {
+ final int mode = change.getMode();
+ nonBackOpen |= TransitionUtil.isOpeningMode(mode);
+ nonBackClose |= TransitionUtil.isClosingMode(mode);
+ }
+ }
+ if (nonBackClose && nonBackOpen) {
+ for (int j = info.getChanges().size() - 1; j >= 0; --j) {
+ final TransitionInfo.Change change = info.getChanges().get(j);
+ if (isSameChangeTarget(openComponent, openTaskId, change)) {
+ info.getChanges().remove(j);
+ } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
+ info.getChanges().remove(j);
+ }
+ }
+ }
+ }
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation transition, merge pending "
+ + "transitions result=%s", info);
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!isGestureBackTransition(info)) {
- if (mOpeningRunning) {
+ if (mClosePrepareTransition == transition) {
+ mClosePrepareTransition = null;
+ }
+ // try to handle unexpected transition
+ mergePendingTransitions(info);
+
+ if (!isGestureBackTransition(info) || shouldCancelAnimation(info)
+ || !mCloseTransitionRequested) {
+ if (mPrepareOpenTransition != null) {
applyFinishOpenTransition();
}
if (mQueuedTransition != null) {
@@ -1222,7 +1382,7 @@
// animation was done
applyFinishOpenTransition();
mCloseTransitionRequested = false;
- } // else, let queued transition to play
+ } // let queued transition finish.
} else {
// we are animating, wait until animation finish
mOnAnimationFinishCallback = () -> {
@@ -1233,6 +1393,56 @@
}
}
+ // Cancel close animation if something happen unexpected, let another handler to handle
+ private boolean shouldCancelAnimation(@NonNull TransitionInfo info) {
+ final boolean noCloseAllowed =
+ info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
+ boolean unableToHandle = false;
+ boolean filterTargets = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ final boolean backGestureAnimated = c.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
+ if (!backGestureAnimated && !c.hasFlags(FLAG_IS_WALLPAPER)) {
+ // something we cannot handle?
+ unableToHandle = true;
+ filterTargets = true;
+ } else if (noCloseAllowed && backGestureAnimated
+ && TransitionUtil.isClosingMode(c.getMode())) {
+ // Prepare back navigation shouldn't contain close change, unless top app
+ // request close.
+ unableToHandle = true;
+ }
+ }
+ if (!unableToHandle) {
+ return false;
+ }
+ if (!filterTargets) {
+ return true;
+ }
+ if (TransitionUtil.isOpeningType(info.getType())
+ || TransitionUtil.isClosingType(info.getType())) {
+ boolean removeWallpaper = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ // filter out opening target, keep original closing target in this transition
+ if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+ && TransitionUtil.isOpeningMode(c.getMode())) {
+ info.getChanges().remove(i);
+ removeWallpaper |= c.hasFlags(FLAG_SHOW_WALLPAPER);
+ }
+ }
+ if (removeWallpaper) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (c.hasFlags(FLAG_IS_WALLPAPER)) {
+ info.getChanges().remove(i);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
/**
* Check whether this transition is prepare for predictive back animation, which could
* happen when core make an activity become visible.
@@ -1247,9 +1457,11 @@
}
SurfaceControl openingLeash = null;
- for (int i = mApps.length - 1; i >= 0; --i) {
- if (mApps[i].mode == MODE_OPENING) {
- openingLeash = mApps[i].leash;
+ if (mApps != null) {
+ for (int i = mApps.length - 1; i >= 0; --i) {
+ if (mApps[i].mode == MODE_OPENING) {
+ openingLeash = mApps[i].leash;
+ }
}
}
if (openingLeash != null) {
@@ -1259,13 +1471,14 @@
final Point offset = c.getEndRelOffset();
st.setPosition(c.getLeash(), offset.x, offset.y);
st.reparent(c.getLeash(), openingLeash);
+ st.setAlpha(c.getLeash(), 1.0f);
}
}
}
st.apply();
mFinishOpenTransaction = ft;
mFinishOpenTransitionCallback = finishCallback;
- mOpeningRunning = true;
+ mOpenTransitionInfo = info;
return true;
}
@@ -1288,6 +1501,10 @@
@NonNull SurfaceControl.Transaction st,
@NonNull SurfaceControl.Transaction ft,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION
+ || !mCloseTransitionRequested) {
+ return false;
+ }
SurfaceControl openingLeash = null;
SurfaceControl closingLeash = null;
for (int i = mApps.length - 1; i >= 0; --i) {
@@ -1325,7 +1542,12 @@
public WindowContainerTransaction handleRequest(
@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (request.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ final int type = request.getType();
+ if (type == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ mPrepareOpenTransition = transition;
+ return new WindowContainerTransaction();
+ }
+ if (type == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
return new WindowContainerTransaction();
}
if (TransitionUtil.isClosingType(request.getType()) && mCloseTransitionRequested) {
@@ -1369,4 +1591,36 @@
}
}
}
+
+ private static ComponentName findComponentName(TransitionInfo.Change change) {
+ final ComponentName componentName = change.getActivityComponent();
+ if (componentName != null) {
+ return componentName;
+ }
+ final TaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null) {
+ return taskInfo.topActivity;
+ }
+ return null;
+ }
+
+ private static int findTaskId(TransitionInfo.Change change) {
+ final TaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null) {
+ return taskInfo.taskId;
+ }
+ return INVALID_TASK_ID;
+ }
+
+ private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
+ TransitionInfo.Change change) {
+ final ComponentName openChange = findComponentName(change);
+ final int firstTaskId = findTaskId(change);
+ return (openChange != null && openChange == topActivity)
+ || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId);
+ }
+
+ private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
+ return findComponentName(change) != null || findTaskId(change) != INVALID_TASK_ID;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 434b512..b6da761 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -174,6 +174,7 @@
BubbleBarUpdate getInitialState() {
BubbleBarUpdate bubbleBarUpdate = BubbleBarUpdate.createInitialState();
bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
+ bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty();
for (int i = 0; i < bubbles.size(); i++) {
bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 8f8b77b..efa1031 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2004,6 +2004,7 @@
// and then remove our views (removing the icon view triggers the removal of the
// bubble window so do that at the end of the animation so we see the scrim animate).
BadgedImageView iconView = bubble.getIconView();
+ final BubbleViewProvider expandedBubbleBeforeScrim = mExpandedBubble;
showScrim(false, () -> {
mRemovingLastBubbleWhileExpanded = false;
bubble.cleanupExpandedView();
@@ -2012,7 +2013,17 @@
}
bubble.cleanupViews(); // cleans up the icon view
updateExpandedView(); // resets state for no expanded bubble
- mExpandedBubble = null;
+ // Bubble keys may not have changed if we receive an update to the same bubble.
+ // Compare bubble object instances to see if the expanded bubble has changed.
+ if (expandedBubbleBeforeScrim == mExpandedBubble) {
+ // Only clear expanded bubble if it has not changed since the scrim animation
+ // started.
+ // Scrim animation can take some time run and it is possible for a new bubble
+ // to be added while the animation is running. This causes the expanded
+ // bubble to change. Make sure we only clear the expanded bubble if it did
+ // not change between when the scrim animation started and completed.
+ mExpandedBubble = null;
+ }
});
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 24c568c..c374478 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -194,6 +194,13 @@
@Override
public void onMenuVisibilityChanged(boolean visible) {
setObscured(visible);
+ if (visible) {
+ mHandleView.setFocusable(false);
+ mHandleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ } else {
+ mHandleView.setFocusable(true);
+ mHandleView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
index 00b9777..1c71ef4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -64,7 +64,7 @@
void update(Icon icon, String title, @ColorInt int tint) {
if (tint == Color.TRANSPARENT) {
final TypedArray typedArray = getContext().obtainStyledAttributes(
- new int[]{android.R.attr.textColorPrimary});
+ new int[]{com.android.internal.R.attr.materialColorOnSurface});
mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
} else {
icon.setTint(tint);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index d5f4924..8389c81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -17,6 +17,7 @@
import android.annotation.ColorInt;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Icon;
@@ -27,6 +28,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.core.widget.ImageViewCompat;
+
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
@@ -39,6 +42,7 @@
private ViewGroup mBubbleSectionView;
private ViewGroup mActionsSectionView;
private ImageView mBubbleIconView;
+ private ImageView mBubbleDismissIconView;
private TextView mBubbleTitleView;
public BubbleBarMenuView(Context context) {
@@ -65,13 +69,18 @@
mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
- updateActionsBackgroundColor();
+ mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
+ updateThemeColors();
}
- private void updateActionsBackgroundColor() {
+ private void updateThemeColors() {
try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
- com.android.internal.R.attr.materialColorSurfaceBright})) {
+ com.android.internal.R.attr.materialColorSurfaceBright,
+ com.android.internal.R.attr.materialColorOnSurface
+ })) {
mActionsSectionView.getBackground().setTint(ta.getColor(0, Color.WHITE));
+ ImageViewCompat.setImageTintList(mBubbleDismissIconView,
+ ColorStateList.valueOf(ta.getColor(1, Color.BLACK)));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 02918db..0d72998 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -19,12 +19,13 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import androidx.core.content.ContextCompat;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce;
@@ -172,12 +173,17 @@
private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
Resources resources = mContext.getResources();
-
+ int tintColor;
+ try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+ com.android.internal.R.attr.materialColorOnSurface})) {
+ tintColor = ta.getColor(0, Color.TRANSPARENT);
+ }
if (bubble.isConversation()) {
// Don't bubble conversation action
menuActions.add(new BubbleBarMenuView.MenuAction(
Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
resources.getString(R.string.bubbles_dont_bubble_conversation),
+ tintColor,
view -> {
hideMenu(true /* animated */);
if (mListener != null) {
@@ -204,7 +210,7 @@
menuActions.add(new BubbleBarMenuView.MenuAction(
Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
resources.getString(R.string.bubble_dismiss_text),
- ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+ tintColor,
view -> {
hideMenu(true /* animated */);
if (mListener != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 1fb0e17..c4c177c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -47,6 +47,8 @@
private final SparseArray<PerDisplay> mInsetsPerDisplay = new SparseArray<>();
private final SparseArray<CopyOnWriteArrayList<OnInsetsChangedListener>> mListeners =
new SparseArray<>();
+ private final CopyOnWriteArrayList<OnInsetsChangedListener> mGlobalListeners =
+ new CopyOnWriteArrayList<>();
public DisplayInsetsController(IWindowManager wmService,
ShellInit shellInit,
@@ -81,6 +83,16 @@
}
/**
+ * Adds a callback to listen for insets changes for any display. Note that the
+ * listener will not be updated with the existing state of the insets on any display.
+ */
+ public void addGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
+ if (!mGlobalListeners.contains(listener)) {
+ mGlobalListeners.add(listener);
+ }
+ }
+
+ /**
* Removes a callback listening for insets changes from a particular display.
*/
public void removeInsetsChangedListener(int displayId, OnInsetsChangedListener listener) {
@@ -91,6 +103,13 @@
listeners.remove(listener);
}
+ /**
+ * Removes a callback listening for insets changes from any display.
+ */
+ public void removeGlobalInsetsChangedListener(OnInsetsChangedListener listener) {
+ mGlobalListeners.remove(listener);
+ }
+
@Override
public void onDisplayAdded(int displayId) {
PerDisplay pd = new PerDisplay(displayId);
@@ -138,12 +157,17 @@
private void insetsChanged(InsetsState insetsState) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
- if (listeners == null) {
+ if (listeners == null && mGlobalListeners.isEmpty()) {
return;
}
mDisplayController.updateDisplayInsets(mDisplayId, insetsState);
- for (OnInsetsChangedListener listener : listeners) {
- listener.insetsChanged(insetsState);
+ for (OnInsetsChangedListener listener : mGlobalListeners) {
+ listener.insetsChanged(mDisplayId, insetsState);
+ }
+ if (listeners != null) {
+ for (OnInsetsChangedListener listener : listeners) {
+ listener.insetsChanged(mDisplayId, insetsState);
+ }
}
}
@@ -285,6 +309,13 @@
default void insetsChanged(InsetsState insetsState) {}
/**
+ * Called when the window insets configuration has changed for the given display.
+ */
+ default void insetsChanged(int displayId, InsetsState insetsState) {
+ insetsChanged(insetsState);
+ }
+
+ /**
* Called when this window retrieved control over a specified set of insets sources.
*/
default void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 19a109e..e2988bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -23,7 +23,10 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.wm.shell.common.split.SplitLayout.BEHIND_APP_VEIL_LAYER;
+import static com.android.wm.shell.common.split.SplitLayout.FRONT_APP_VEIL_LAYER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
+import static com.android.wm.shell.common.split.SplitScreenConstants.VEIL_DELAY_DURATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -74,7 +77,7 @@
private final SurfaceSession mSurfaceSession;
private Drawable mIcon;
- private ImageView mResizingIconView;
+ private ImageView mVeilIconView;
private SurfaceControlViewHost mViewHost;
private SurfaceControl mHostLeash;
private SurfaceControl mIconLeash;
@@ -83,13 +86,14 @@
private SurfaceControl mScreenshot;
private boolean mShown;
- private boolean mIsResizing;
+ /** True if the task is going through some kind of transition (moving or changing size). */
+ private boolean mIsCurrentlyChanging;
/** The original bounds of the main task, captured at the beginning of a resize transition. */
private final Rect mOldMainBounds = new Rect();
/** The original bounds of the side task, captured at the beginning of a resize transition. */
private final Rect mOldSideBounds = new Rect();
/** The current bounds of the main task, mid-resize. */
- private final Rect mResizingBounds = new Rect();
+ private final Rect mInstantaneousBounds = new Rect();
private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
private ValueAnimator mScreenshotAnimator;
@@ -134,7 +138,7 @@
mIconSize = context.getResources().getDimensionPixelSize(R.dimen.split_icon_size);
final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(context)
.inflate(R.layout.split_decor, null);
- mResizingIconView = rootLayout.findViewById(R.id.split_resizing_icon);
+ mVeilIconView = rootLayout.findViewById(R.id.split_resizing_icon);
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
@@ -191,28 +195,28 @@
}
mHostLeash = null;
mIcon = null;
- mResizingIconView = null;
- mIsResizing = false;
+ mVeilIconView = null;
+ mIsCurrentlyChanging = false;
mShown = false;
mOldMainBounds.setEmpty();
mOldSideBounds.setEmpty();
- mResizingBounds.setEmpty();
+ mInstantaneousBounds.setEmpty();
}
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
boolean immediately) {
- if (mResizingIconView == null) {
+ if (mVeilIconView == null) {
return;
}
- if (!mIsResizing) {
- mIsResizing = true;
+ if (!mIsCurrentlyChanging) {
+ mIsCurrentlyChanging = true;
mOldMainBounds.set(newBounds);
mOldSideBounds.set(sideBounds);
}
- mResizingBounds.set(newBounds);
+ mInstantaneousBounds.set(newBounds);
mOffsetX = offsetX;
mOffsetY = offsetY;
@@ -254,8 +258,8 @@
if (mIcon == null && resizingTask.topActivityInfo != null) {
mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
- mResizingIconView.setImageDrawable(mIcon);
- mResizingIconView.setVisibility(View.VISIBLE);
+ mVeilIconView.setImageDrawable(mIcon);
+ mVeilIconView.setVisibility(View.VISIBLE);
WindowManager.LayoutParams lp =
(WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
@@ -275,7 +279,12 @@
t.setAlpha(mIconLeash, showVeil ? 1f : 0f);
t.setVisibility(mIconLeash, showVeil);
} else {
- startFadeAnimation(showVeil, false, null);
+ startFadeAnimation(
+ showVeil,
+ false /* releaseSurface */,
+ null /* finishedCallback */,
+ false /* addDelay */
+ );
}
mShown = showVeil;
}
@@ -320,19 +329,19 @@
mScreenshotAnimator.start();
}
- if (mResizingIconView == null) {
+ if (mVeilIconView == null) {
if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
animFinishedCallback.accept(false);
}
return;
}
- mIsResizing = false;
+ mIsCurrentlyChanging = false;
mOffsetX = 0;
mOffsetY = 0;
mOldMainBounds.setEmpty();
mOldSideBounds.setEmpty();
- mResizingBounds.setEmpty();
+ mInstantaneousBounds.setEmpty();
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
if (!mShown) {
// If fade-out animation is running, just add release callback to it.
@@ -356,7 +365,7 @@
if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
animFinishedCallback.accept(true);
}
- });
+ }, false /* addDelay */);
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
@@ -366,9 +375,94 @@
}
}
+ /**
+ * Called (on every frame) when two split apps are swapping, and a veil is needed.
+ */
+ public void drawNextVeilFrameForSwapAnimation(ActivityManager.RunningTaskInfo resizingTask,
+ Rect newBounds, SurfaceControl.Transaction t, boolean isGoingBehind,
+ SurfaceControl leash, float iconOffsetX, float iconOffsetY) {
+ if (mVeilIconView == null) {
+ return;
+ }
+
+ if (!mIsCurrentlyChanging) {
+ mIsCurrentlyChanging = true;
+ }
+
+ mInstantaneousBounds.set(newBounds);
+ mOffsetX = (int) iconOffsetX;
+ mOffsetY = (int) iconOffsetY;
+
+ t.setLayer(leash, isGoingBehind ? BEHIND_APP_VEIL_LAYER : FRONT_APP_VEIL_LAYER);
+
+ if (!mShown) {
+ if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ // Cancel mFadeAnimator if it is running
+ mFadeAnimator.cancel();
+ }
+ }
+
+ if (mBackgroundLeash == null) {
+ // Initialize background
+ mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+ RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+ t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
+ .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
+ }
+
+ if (mIcon == null && resizingTask.topActivityInfo != null) {
+ // Initialize icon
+ mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
+ mVeilIconView.setImageDrawable(mIcon);
+ mVeilIconView.setVisibility(View.VISIBLE);
+
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = mIconSize;
+ lp.height = mIconSize;
+ mViewHost.relayout(lp);
+
+ t.setLayer(mIconLeash, Integer.MAX_VALUE);
+ }
+
+ t.setPosition(mIconLeash,
+ newBounds.width() / 2 - mIconSize / 2 - mOffsetX,
+ newBounds.height() / 2 - mIconSize / 2 - mOffsetY);
+
+ // If this is the first frame, we need to trigger the veil's fade-in animation.
+ if (!mShown) {
+ startFadeAnimation(
+ true /* show */,
+ false /* releaseSurface */,
+ null /* finishedCallball */,
+ false /* addDelay */
+ );
+ mShown = true;
+ }
+ }
+
+ /** Called at the end of the swap animation. */
+ public void fadeOutVeilAndCleanUp(SurfaceControl.Transaction t) {
+ if (mVeilIconView == null) {
+ return;
+ }
+
+ // Recenter icon
+ t.setPosition(mIconLeash,
+ mInstantaneousBounds.width() / 2f - mIconSize / 2f,
+ mInstantaneousBounds.height() / 2f - mIconSize / 2f);
+
+ mIsCurrentlyChanging = false;
+ mOffsetX = 0;
+ mOffsetY = 0;
+ mInstantaneousBounds.setEmpty();
+
+ fadeOutDecor(() -> {}, true /* addDelay */);
+ }
+
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
- if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
+ if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
mScreenshotAnimator.cancel();
} else if (mScreenshot != null) {
@@ -386,7 +480,7 @@
public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
if (screenshot == null || !screenshot.isValid()) return;
- if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) {
+ if (!mShown && mIsCurrentlyChanging && !mOldMainBounds.equals(mInstantaneousBounds)) {
if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
mScreenshotAnimator.cancel();
} else if (mScreenshot != null) {
@@ -401,24 +495,35 @@
/** Fade-out decor surface with animation end callback, if decor is hidden, run the callback
* directly. */
- public void fadeOutDecor(Runnable finishedCallback) {
+ public void fadeOutDecor(Runnable finishedCallback, boolean addDelay) {
if (mShown) {
// If previous animation is running, just cancel it.
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
mFadeAnimator.cancel();
}
- startFadeAnimation(false /* show */, true, finishedCallback);
+ startFadeAnimation(
+ false /* show */, true /* releaseSurface */, finishedCallback, addDelay);
mShown = false;
} else {
if (finishedCallback != null) finishedCallback.run();
}
}
+ /**
+ * Fades the veil in or out. Called at the first frame of a movement or resize when a veil is
+ * needed (with show = true), and called again at the end (with show = false).
+ * @param addDelay If true, adds a short delay before fading out to get the app behind the veil
+ * time to redraw.
+ */
private void startFadeAnimation(boolean show, boolean releaseSurface,
- Runnable finishedCallback) {
+ Runnable finishedCallback, boolean addDelay) {
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+
mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
+ if (addDelay) {
+ mFadeAnimator.setStartDelay(VEIL_DELAY_DURATION);
+ }
mFadeAnimator.setDuration(FADE_DURATION);
mFadeAnimator.addUpdateListener(valueAnimator-> {
final float progress = (float) valueAnimator.getAnimatedValue();
@@ -481,8 +586,8 @@
}
if (mIcon != null) {
- mResizingIconView.setVisibility(View.GONE);
- mResizingIconView.setImageDrawable(null);
+ mVeilIconView.setVisibility(View.GONE);
+ mVeilIconView.setImageDrawable(null);
t.hide(mIconLeash);
mIcon = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 51f9de8..0e050694 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -53,6 +53,8 @@
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -68,10 +70,12 @@
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageTaskListener;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -87,10 +91,29 @@
public static final int PARALLAX_ALIGN_CENTER = 2;
public static final int FLING_RESIZE_DURATION = 250;
- private static final int FLING_SWITCH_DURATION = 350;
private static final int FLING_ENTER_DURATION = 450;
private static final int FLING_EXIT_DURATION = 450;
+ // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
+ // layers stay in order. Note: This does not affect any other layer numbering systems because
+ // the layer system in WindowManager is local within sibling groups. So, for example, each
+ // "veil layer" defined here actually has two sub-layers; and *their* layer values, which we set
+ // in SplitDecorManager, are only important relative to each other.
+ public static final int DIVIDER_LAYER = 0;
+ public static final int FRONT_APP_VEIL_LAYER = DIVIDER_LAYER + 20;
+ public static final int FRONT_APP_LAYER = DIVIDER_LAYER + 10;
+ public static final int BEHIND_APP_VEIL_LAYER = DIVIDER_LAYER - 10;
+ public static final int BEHIND_APP_LAYER = DIVIDER_LAYER - 20;
+
+ // Animation specs for the swap animation
+ private static final int SWAP_ANIMATION_TOTAL_DURATION = 500;
+ private static final float SWAP_ANIMATION_SHRINK_DURATION = 83;
+ private static final float SWAP_ANIMATION_SHRINK_MARGIN_DP = 14;
+ private static final Interpolator SHRINK_INTERPOLATOR =
+ new PathInterpolator(0.2f, 0f, 0f, 1f);
+ private static final Interpolator GROW_INTERPOLATOR =
+ new PathInterpolator(0.45f, 0f, 0.5f, 1f);
+
private int mDividerWindowWidth;
private int mDividerInsets;
private int mDividerSize;
@@ -134,6 +157,7 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mIsLeftRightSplit;
private ValueAnimator mDividerFlingAnimator;
+ private AnimatorSet mSwapAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
@@ -579,6 +603,10 @@
}
void onDoubleTappedDivider() {
+ if (isCurrentlySwapping()) {
+ return;
+ }
+
mSplitLayoutHandler.onDoubleTappedDivider();
}
@@ -685,36 +713,43 @@
}
/** Switch both surface position with animation. */
- public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
- SurfaceControl leash2, Consumer<Rect> finishCallback) {
+ public void playSwapAnimation(SurfaceControl.Transaction t, StageTaskListener topLeftStage,
+ StageTaskListener bottomRightStage, Consumer<Rect> finishCallback) {
final Rect insets = getDisplayStableInsets(mContext);
+ // If we have insets in the direction of the swap, the animation won't look correct because
+ // window contents will shift and redraw again at the end. So we show a veil to hide that.
insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
+ final boolean shouldVeil =
+ insets.left != 0 || insets.top != 0 || insets.right != 0 || insets.bottom != 0;
final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
- final Rect distBounds1 = new Rect();
- final Rect distBounds2 = new Rect();
- final Rect distDividerBounds = new Rect();
- // Compute dist bounds.
- updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds,
+ final Rect endBounds1 = new Rect();
+ final Rect endBounds2 = new Rect();
+ final Rect endDividerBounds = new Rect();
+ // Compute destination bounds.
+ updateBounds(dividerPos, endBounds2, endBounds1, endDividerBounds,
false /* setEffectBounds */);
// Offset to real position under root container.
- distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
- distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
- distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ endBounds1.offset(-mRootBounds.left, -mRootBounds.top);
+ endBounds2.offset(-mRootBounds.left, -mRootBounds.top);
+ endDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
- ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
- -insets.left, -insets.top);
- ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
- insets.left, insets.top);
- ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
- distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
+ ValueAnimator animator1 = moveSurface(t, topLeftStage, getRefBounds1(), endBounds1,
+ -insets.left, -insets.top, true /* roundCorners */, true /* isGoingBehind */,
+ shouldVeil);
+ ValueAnimator animator2 = moveSurface(t, bottomRightStage, getRefBounds2(), endBounds2,
+ insets.left, insets.top, true /* roundCorners */, false /* isGoingBehind */,
+ shouldVeil);
+ ValueAnimator animator3 = moveSurface(t, null /* stage */, getRefDividerBounds(),
+ endDividerBounds, 0 /* offsetX */, 0 /* offsetY */, false /* roundCorners */,
+ false /* isGoingBehind */, false /* addVeil */);
- AnimatorSet set = new AnimatorSet();
- set.playTogether(animator1, animator2, animator3);
- set.setDuration(FLING_SWITCH_DURATION);
- set.addListener(new AnimatorListenerAdapter() {
+ mSwapAnimator = new AnimatorSet();
+ mSwapAnimator.playTogether(animator1, animator2, animator3);
+ mSwapAnimator.setDuration(SWAP_ANIMATION_TOTAL_DURATION);
+ mSwapAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mInteractionJankMonitor.begin(getDividerLeash(),
@@ -734,36 +769,144 @@
mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER);
}
});
- set.start();
+ mSwapAnimator.start();
}
- private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
- Rect start, Rect end, float offsetX, float offsetY) {
+ /** Returns true if a swap animation is currently playing. */
+ public boolean isCurrentlySwapping() {
+ return mSwapAnimator != null && mSwapAnimator.isRunning();
+ }
+
+ /**
+ * Animates a task leash across the screen. Currently used only for the swap animation.
+ *
+ * @param stage The stage holding the task being animated. If null, it is the divider.
+ * @param roundCorners Whether we should round the corners of the task while animating.
+ * @param isGoingBehind Whether we should a shrink-and-grow effect to the task while it is
+ * moving. (Simulates moving behind the divider.)
+ */
+ private ValueAnimator moveSurface(SurfaceControl.Transaction t, StageTaskListener stage,
+ Rect start, Rect end, float offsetX, float offsetY, boolean roundCorners,
+ boolean isGoingBehind, boolean addVeil) {
+ final boolean isApp = stage != null; // check if this is an app or a divider
+ final SurfaceControl leash = isApp ? stage.getRootLeash() : getDividerLeash();
+ final ActivityManager.RunningTaskInfo taskInfo = isApp ? stage.getRunningTaskInfo() : null;
+ final SplitDecorManager decorManager = isApp ? stage.getDecorManager() : null;
+
Rect tempStart = new Rect(start);
Rect tempEnd = new Rect(end);
final float diffX = tempEnd.left - tempStart.left;
final float diffY = tempEnd.top - tempStart.top;
final float diffWidth = tempEnd.width() - tempStart.width();
final float diffHeight = tempEnd.height() - tempStart.height();
+
+ // Get display measurements (for possible shrink animation).
+ final RoundedCorner roundedCorner = mSplitWindowManager.getDividerView().getDisplay()
+ .getRoundedCorner(0 /* position */);
+ float cornerRadius = roundedCorner == null ? 0 : roundedCorner.getRadius();
+ float shrinkMarginPx = PipUtils.dpToPx(
+ SWAP_ANIMATION_SHRINK_MARGIN_DP, mContext.getResources().getDisplayMetrics());
+ float shrinkAmountPx = shrinkMarginPx * 2;
+
+ // Timing calculations
+ float shrinkPortion = SWAP_ANIMATION_SHRINK_DURATION / SWAP_ANIMATION_TOTAL_DURATION;
+ float growPortion = 1 - shrinkPortion;
+
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.setInterpolator(Interpolators.EMPHASIZED);
animator.addUpdateListener(animation -> {
if (leash == null) return;
+ if (roundCorners) {
+ // Add rounded corners to the task leash while it is animating.
+ t.setCornerRadius(leash, cornerRadius);
+ }
- final float scale = (float) animation.getAnimatedValue();
- final float distX = tempStart.left + scale * diffX;
- final float distY = tempStart.top + scale * diffY;
- final int width = (int) (tempStart.width() + scale * diffWidth);
- final int height = (int) (tempStart.height() + scale * diffHeight);
- if (offsetX == 0 && offsetY == 0) {
- t.setPosition(leash, distX, distY);
- t.setWindowCrop(leash, width, height);
+ final float progress = (float) animation.getAnimatedValue();
+ float instantaneousX = tempStart.left + progress * diffX;
+ float instantaneousY = tempStart.top + progress * diffY;
+ int width = (int) (tempStart.width() + progress * diffWidth);
+ int height = (int) (tempStart.height() + progress * diffHeight);
+
+ if (isGoingBehind) {
+ float shrinkDiffX; // the position adjustments needed for this frame
+ float shrinkDiffY;
+ float shrinkScaleX; // the scale adjustments needed for this frame
+ float shrinkScaleY;
+
+ // Find the max amount we will be shrinking this leash, as a proportion (e.g. 0.1f).
+ float maxShrinkX = shrinkAmountPx / height;
+ float maxShrinkY = shrinkAmountPx / width;
+
+ // Find if we are in the shrinking part of the animation, or the growing part.
+ boolean shrinking = progress <= shrinkPortion;
+
+ if (shrinking) {
+ // Find how far into the shrink portion we are (e.g. 0.5f).
+ float shrinkProgress = progress / shrinkPortion;
+ // Find how much we should have progressed in shrinking the leash (e.g. 0.8f).
+ float interpolatedShrinkProgress =
+ SHRINK_INTERPOLATOR.getInterpolation(shrinkProgress);
+ // Find how much width proportion we should be taking off (e.g. 0.1f)
+ float widthProportionLost = maxShrinkX * interpolatedShrinkProgress;
+ shrinkScaleX = 1 - widthProportionLost;
+ // Find how much height proportion we should be taking off (e.g. 0.1f)
+ float heightProportionLost = maxShrinkY * interpolatedShrinkProgress;
+ shrinkScaleY = 1 - heightProportionLost;
+ // Add a small amount to the leash's position to keep the task centered.
+ shrinkDiffX = (width * widthProportionLost) / 2;
+ shrinkDiffY = (height * heightProportionLost) / 2;
+ } else {
+ // Find how far into the grow portion we are (e.g. 0.5f).
+ float growProgress = (progress - shrinkPortion) / growPortion;
+ // Find how much we should have progressed in growing the leash (e.g. 0.8f).
+ float interpolatedGrowProgress =
+ GROW_INTERPOLATOR.getInterpolation(growProgress);
+ // Find how much width proportion we should be taking off (e.g. 0.1f)
+ float widthProportionLost = maxShrinkX * (1 - interpolatedGrowProgress);
+ shrinkScaleX = 1 - widthProportionLost;
+ // Find how much height proportion we should be taking off (e.g. 0.1f)
+ float heightProportionLost = maxShrinkY * (1 - interpolatedGrowProgress);
+ shrinkScaleY = 1 - heightProportionLost;
+ // Add a small amount to the leash's position to keep the task centered.
+ shrinkDiffX = (width * widthProportionLost) / 2;
+ shrinkDiffY = (height * heightProportionLost) / 2;
+ }
+
+ instantaneousX += shrinkDiffX;
+ instantaneousY += shrinkDiffY;
+ width *= shrinkScaleX;
+ height *= shrinkScaleY;
+ // Set scale on the leash's contents.
+ t.setScale(leash, shrinkScaleX, shrinkScaleY);
+ }
+
+ // Set layers
+ if (taskInfo != null) {
+ t.setLayer(leash, isGoingBehind ? BEHIND_APP_LAYER : FRONT_APP_LAYER);
} else {
- final int diffOffsetX = (int) (scale * offsetX);
- final int diffOffsetY = (int) (scale * offsetY);
- t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);
+ t.setLayer(leash, DIVIDER_LAYER);
+ }
+
+ if (offsetX == 0 && offsetY == 0) {
+ t.setPosition(leash, instantaneousX, instantaneousY);
+ mTempRect.set((int) instantaneousX, (int) instantaneousY,
+ (int) (instantaneousX + width), (int) (instantaneousY + height));
+ t.setWindowCrop(leash, width, height);
+ if (addVeil) {
+ decorManager.drawNextVeilFrameForSwapAnimation(
+ taskInfo, mTempRect, t, isGoingBehind, leash, 0, 0);
+ }
+ } else {
+ final int diffOffsetX = (int) (progress * offsetX);
+ final int diffOffsetY = (int) (progress * offsetY);
+ t.setPosition(leash, instantaneousX + diffOffsetX, instantaneousY + diffOffsetY);
mTempRect.set(0, 0, width, height);
mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
t.setCrop(leash, mTempRect);
+ if (addVeil) {
+ decorManager.drawNextVeilFrameForSwapAnimation(
+ taskInfo, mTempRect, t, isGoingBehind, leash, diffOffsetX, diffOffsetY);
+ }
}
t.apply();
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index e8c809e..8c06de7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -29,6 +29,8 @@
public class SplitScreenConstants {
/** Duration used for every split fade-in or fade-out. */
public static final int FADE_DURATION = 133;
+ /** Duration where we keep an app veiled to allow it to redraw itself behind the scenes. */
+ public static final int VEIL_DELAY_DURATION = 400;
/** Key for passing in widget intents when invoking split from launcher workspace. */
public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 7c0455e..c2ee223 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -186,6 +186,9 @@
*/
private boolean mIsFirstReachabilityEducationRunning;
+ @NonNull
+ private final CompatUIStatusManager mCompatUIStatusManager;
+
public CompatUIController(@NonNull Context context,
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -198,7 +201,8 @@
@NonNull DockStateReader dockStateReader,
@NonNull CompatUIConfiguration compatUIConfiguration,
@NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
- @NonNull AccessibilityManager accessibilityManager) {
+ @NonNull AccessibilityManager accessibilityManager,
+ @NonNull CompatUIStatusManager compatUIStatusManager) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
@@ -213,6 +217,7 @@
mCompatUIShellCommandHandler = compatUIShellCommandHandler;
mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
DISAPPEAR_DELAY_MS, flags);
+ mCompatUIStatusManager = compatUIStatusManager;
shellInit.addInitCallback(this::onInit, this);
}
@@ -520,7 +525,7 @@
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
mTransitionsLazy.get(),
stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second),
- mDockStateReader, mCompatUIConfiguration);
+ mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
}
private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
new file mode 100644
index 0000000..915a8a1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIStatusManager.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compatui;
+
+import android.annotation.NonNull;
+
+import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
+
+/** Handle the visibility state of the Compat UI components. */
+public class CompatUIStatusManager {
+
+ public static final int COMPAT_UI_EDUCATION_HIDDEN = 0;
+ public static final int COMPAT_UI_EDUCATION_VISIBLE = 1;
+
+ @NonNull
+ private final IntConsumer mWriter;
+ @NonNull
+ private final IntSupplier mReader;
+
+ public CompatUIStatusManager(@NonNull IntConsumer writer, @NonNull IntSupplier reader) {
+ mWriter = writer;
+ mReader = reader;
+ }
+
+ public CompatUIStatusManager() {
+ this(i -> { }, () -> COMPAT_UI_EDUCATION_HIDDEN);
+ }
+
+ void onEducationShown() {
+ mWriter.accept(COMPAT_UI_EDUCATION_VISIBLE);
+ }
+
+ void onEducationHidden() {
+ mWriter.accept(COMPAT_UI_EDUCATION_HIDDEN);
+ }
+
+ boolean isEducationVisible() {
+ return mReader.getAsInt() == COMPAT_UI_EDUCATION_VISIBLE;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index 2347032..3124a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.content.Context;
@@ -76,15 +77,19 @@
private final DockStateReader mDockStateReader;
+ @NonNull
+ private final CompatUIStatusManager mCompatUIStatusManager;
+
LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Transitions transitions,
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
- DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+ @NonNull CompatUIStatusManager compatUIStatusManager) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
onDismissCallback,
new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
- dockStateReader, compatUIConfiguration);
+ dockStateReader, compatUIConfiguration, compatUIStatusManager);
}
@VisibleForTesting
@@ -93,7 +98,8 @@
DisplayLayout displayLayout, Transitions transitions,
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
DialogAnimationController<LetterboxEduDialogLayout> animationController,
- DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration) {
+ DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+ @NonNull CompatUIStatusManager compatUIStatusManager) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mTransitions = transitions;
mOnDismissCallback = onDismissCallback;
@@ -103,6 +109,7 @@
R.dimen.letterbox_education_dialog_margin);
mDockStateReader = dockStateReader;
mCompatUIConfiguration = compatUIConfiguration;
+ mCompatUIStatusManager = compatUIStatusManager;
mEligibleForLetterboxEducation =
taskInfo.appCompatTaskInfo.eligibleForLetterboxEducation();
}
@@ -139,7 +146,7 @@
protected View createLayout() {
mLayout = inflateLayout();
updateDialogMargins();
-
+ mCompatUIStatusManager.onEducationShown();
// startEnterAnimation will be called immediately if shell-transitions are disabled.
mTransitions.runOnIdle(this::startEnterAnimation);
return mLayout;
@@ -199,6 +206,7 @@
@Override
public void release() {
mAnimationController.cancelAnimation();
+ mCompatUIStatusManager.onEducationHidden();
super.release();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS
new file mode 100644
index 0000000..1875675
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS
@@ -0,0 +1,4 @@
+# WM shell sub-module compat ui owners
+mariiasand@google.com
+gracielawputri@google.com
+mcarli@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
index a520d5e..022906c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -16,6 +16,9 @@
package com.android.wm.shell.compatui.api
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+
/**
* Defines the predicates to invoke for understanding if a component can be created or destroyed.
*/
@@ -39,6 +42,7 @@
* Describes each compat ui component to the framework.
*/
class CompatUISpec(
+ val log: (String) -> Unit = { str -> ProtoLog.v(ShellProtoLogGroup.WM_SHELL_COMPAT_UI, str) },
// Unique name for the component. It's used for debug and for generating the
// unique component identifier in the system.
val name: String,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f22dcce..04cd225 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.dagger;
+import static android.provider.Settings.Secure.COMPAT_UI_EDUCATION_SHOWING;
+
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
import static com.android.wm.shell.onehanded.OneHandedController.SUPPORT_ONE_HANDED_MODE;
import android.annotation.NonNull;
@@ -24,6 +27,7 @@
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
import android.window.SystemPerformanceHinter;
@@ -72,6 +76,7 @@
import com.android.wm.shell.compatui.CompatUIConfiguration;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
+import com.android.wm.shell.compatui.CompatUIStatusManager;
import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator;
import com.android.wm.shell.compatui.api.CompatUIHandler;
import com.android.wm.shell.compatui.api.CompatUIRepository;
@@ -254,7 +259,8 @@
Lazy<AccessibilityManager> accessibilityManager,
CompatUIRepository compatUIRepository,
@NonNull CompatUIState compatUIState,
- @NonNull CompatUIComponentIdGenerator componentIdGenerator) {
+ @NonNull CompatUIComponentIdGenerator componentIdGenerator,
+ CompatUIStatusManager compatUIStatusManager) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
@@ -276,7 +282,22 @@
dockStateReader.get(),
compatUIConfiguration.get(),
compatUIShellCommandHandler.get(),
- accessibilityManager.get()));
+ accessibilityManager.get(),
+ compatUIStatusManager));
+ }
+
+ @WMSingleton
+ @Provides
+ static CompatUIStatusManager provideCompatUIStatusManager(@NonNull Context context) {
+ if (Flags.enableCompatUiVisibilityStatus()) {
+ return new CompatUIStatusManager(
+ newState -> Settings.Secure.putInt(context.getContentResolver(),
+ COMPAT_UI_EDUCATION_SHOWING, newState),
+ () -> Settings.Secure.getInt(context.getContentResolver(),
+ COMPAT_UI_EDUCATION_SHOWING, COMPAT_UI_EDUCATION_HIDDEN));
+ } else {
+ return new CompatUIStatusManager();
+ }
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index a18bbad..b8b62a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -32,6 +32,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
+import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -58,6 +59,7 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -68,6 +70,7 @@
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
+import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.GlobalDragListener;
@@ -603,10 +606,12 @@
Context context,
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Optional<DesktopTasksLimiter> desktopTasksLimiter,
InteractionJankMonitor interactionJankMonitor) {
- return new DragToDesktopTransitionHandler(context, transitions,
- rootTaskDisplayAreaOrganizer, interactionJankMonitor);
+ return Flags.enableDesktopWindowingTransitions()
+ ? new SpringDragToDesktopTransitionHandler(context, transitions,
+ rootTaskDisplayAreaOrganizer, interactionJankMonitor)
+ : new DefaultDragToDesktopTransitionHandler(context, transitions,
+ rootTaskDisplayAreaOrganizer, interactionJankMonitor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 3e7b4fe..6c03dc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -54,6 +54,12 @@
// Instead default to the desired initial bounds.
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
+ if (hasFullscreenOverride(taskInfo)) {
+ // If the activity has a fullscreen override applied, it should be treated as
+ // resizeable and match the device orientation. Thus the ideal size can be
+ // applied.
+ return positionInScreen(idealSize, stableBounds)
+ }
val topActivityInfo =
taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds)
@@ -62,13 +68,17 @@
ORIENTATION_LANDSCAPE -> {
if (taskInfo.isResizeable) {
if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
- // Respect apps fullscreen width
+ // For portrait resizeable activities, respect apps fullscreen width but
+ // apply ideal size height.
Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
idealSize.height)
} else {
+ // For landscape resizeable activities, simply apply ideal size.
idealSize
}
} else {
+ // If activity is unresizeable, regardless of orientation, calculate maximum
+ // size (within the ideal size) maintaining original aspect ratio.
maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
}
}
@@ -77,23 +87,29 @@
screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
if (taskInfo.isResizeable) {
if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
- // Respect apps fullscreen height and apply custom app width
+ // For landscape resizeable activities, respect apps fullscreen height and
+ // apply custom app width.
Size(
customPortraitWidthForLandscapeApp,
taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
)
} else {
+ // For portrait resizeable activities, simply apply ideal size.
idealSize
}
} else {
if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
- // Apply custom app width and calculate maximum size
+ // For landscape unresizeable activities, apply custom app width to ideal
+ // size and calculate maximum size with this area while maintaining original
+ // aspect ratio.
maximizeSizeGivenAspectRatio(
taskInfo,
Size(customPortraitWidthForLandscapeApp, idealSize.height),
appAspectRatio
)
} else {
+ // For portrait unresizeable activities, calculate maximum size (within the
+ // ideal size) maintaining original aspect ratio.
maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio)
}
}
@@ -209,3 +225,8 @@
else -> isFixedOrientationPortrait(configuration.orientation)
}
}
+
+private fun hasFullscreenOverride(taskInfo: RunningTaskInfo): Boolean {
+ return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled
+ || taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 6011db7..09f9139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -27,6 +27,7 @@
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -262,12 +263,22 @@
/**
* Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
+ *
+ * @param finishCallback called when animation ends or gets cancelled
*/
- private void fadeOutIndicator() {
+ void fadeOutIndicator(@Nullable Runnable finishCallback) {
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
.fadeBoundsOut(mView, mCurrentType,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
+ if (finishCallback != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishCallback.run();
+ }
+ });
+ }
mCurrentType = IndicatorType.NO_INDICATOR;
}
@@ -282,7 +293,7 @@
if (mCurrentType == IndicatorType.NO_INDICATOR) {
fadeInIndicator(newType);
} else if (newType == IndicatorType.NO_INDICATOR) {
- fadeOutIndicator();
+ fadeOutIndicator(null /* finishCallback */);
} else {
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
index 97abda8..65f12cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
@@ -116,10 +116,10 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition {
return when {
- top == bounds.top && left == bounds.left -> TopLeft
- top == bounds.top && right == bounds.right -> TopRight
- bottom == bounds.bottom && left == bounds.left -> BottomLeft
- bottom == bounds.bottom && right == bounds.right -> BottomRight
+ top == bounds.top && left == bounds.left && bottom != bounds.bottom -> TopLeft
+ top == bounds.top && right == bounds.right && bottom != bounds.bottom -> TopRight
+ bottom == bounds.bottom && left == bounds.left && top != bounds.top -> BottomLeft
+ bottom == bounds.bottom && right == bounds.right && top != bounds.top -> BottomRight
else -> Center
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index e154da5..f54b44b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -163,8 +163,10 @@
}
private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
- visualIndicator?.releaseVisualIndicator(tx)
- visualIndicator = null
+ visualIndicator?.fadeOutIndicator {
+ visualIndicator?.releaseVisualIndicator(tx)
+ visualIndicator = null
+ }
}
}
@@ -193,7 +195,7 @@
)
transitions.addHandler(this)
taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
- dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener)
+ dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener
recentsTransitionHandler.addTransitionStateListener(
object : RecentsTransitionStateListener {
override fun onAnimationStateChanged(running: Boolean) {
@@ -213,7 +215,7 @@
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
- dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
+ dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener
}
fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -1068,6 +1070,11 @@
// In some launches home task is moved behind new task being launched. Make sure
// that's not the case for launches in desktop.
moveHomeTask(wct, toTop = false)
+ // Move existing minimized tasks behind Home
+ taskRepository.getFreeformTasksInZOrder(task.displayId)
+ .filter { taskId -> taskRepository.isMinimizedTask(taskId) }
+ .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
+ .forEach { taskInfo -> wct.reorder(taskInfo.token, /* onTop= */ false) }
// Desktop Mode is already showing and we're launching a new Task - we might need to
// minimize another Task.
val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 5221a45..9874f4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -27,19 +27,21 @@
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
+import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.animation.FloatProperties
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition
import com.android.wm.shell.protolog.ShellProtoLogGroup
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
@@ -50,40 +52,31 @@
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.function.Supplier
+import kotlin.math.max
/**
* Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also
* handles the cancellation case where the task is dragged back to the status bar area in the same
* gesture.
+ *
+ * It's a base sealed class that delegates flag dependant logic to its subclasses:
+ * [DefaultDragToDesktopTransitionHandler] and [SpringDragToDesktopTransitionHandler]
+ *
+ * TODO(b/356764679): Clean up after the full flag rollout
*/
-class DragToDesktopTransitionHandler(
+sealed class DragToDesktopTransitionHandler(
private val context: Context,
private val transitions: Transitions,
private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- private val interactionJankMonitor: InteractionJankMonitor,
- private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ protected val interactionJankMonitor: InteractionJankMonitor,
+ protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {
- constructor(
- context: Context,
- transitions: Transitions,
- rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
- interactionJankMonitor: InteractionJankMonitor
- ) : this(
- context,
- transitions,
- rootTaskDisplayAreaOrganizer,
- interactionJankMonitor,
- Supplier { SurfaceControl.Transaction() }
- )
-
- private val rectEvaluator = RectEvaluator(Rect())
+ protected val rectEvaluator = RectEvaluator(Rect())
private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
- private var dragToDesktopStateListener: DragToDesktopStateListener? = null
private lateinit var splitScreenController: SplitScreenController
private var transitionState: TransitionState? = null
- private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
/** Whether a drag-to-desktop transition is in progress. */
val inProgress: Boolean
@@ -92,20 +85,18 @@
/** The task id of the task currently being dragged from fullscreen/split. */
val draggingTaskId: Int
get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID
- /** Sets a listener to receive callback about events during the transition animation. */
- fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) {
- dragToDesktopStateListener = listener
- }
+
+ /** Listener to receive callback about events during the transition animation. */
+ var dragToDesktopStateListener: DragToDesktopStateListener? = null
+
+ /** Task listener for animation start, task bounds resize, and the animation finish */
+ lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
/** Setter needed to avoid cyclic dependency. */
fun setSplitScreenController(controller: SplitScreenController) {
splitScreenController = controller
}
- fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
- onTaskResizeAnimationListener = listener
- }
-
/**
* Starts a transition that performs a transient launch of Home so that Home is brought to the
* front while still keeping the currently focused task that is being dragged resumed. This
@@ -307,24 +298,18 @@
return false
}
- // Layering: non-wallpaper, non-home tasks excluding the dragged task go at the bottom,
- // then Home on top of that, wallpaper on top of that and finally the dragged task on top
- // of everything.
- val appLayers = info.changes.size
- val homeLayers = info.changes.size * 2
- val wallpaperLayers = info.changes.size * 3
- val dragLayer = wallpaperLayers
+ val layers = calculateStartDragToDesktopLayers(info)
val leafTaskFilter = TransitionUtil.LeafTaskFilter()
info.changes.withIndex().forEach { (i, change) ->
if (TransitionUtil.isWallpaper(change)) {
- val layer = wallpaperLayers - i
+ val layer = layers.wallpaperLayers - i
startTransaction.apply {
setLayer(change.leash, layer)
show(change.leash)
}
} else if (isHomeChange(change)) {
- state.homeToken = change.container
- val layer = homeLayers - i
+ state.homeChange = change
+ val layer = layers.homeLayers - i
startTransaction.apply {
setLayer(change.leash, layer)
show(change.leash)
@@ -338,11 +323,11 @@
if (state.cancelState == CancelState.NO_CANCEL) {
// Normal case, split root goes to the bottom behind everything
// else.
- appLayers - i
+ layers.appLayers - i
} else {
// Cancel-early case, pretend nothing happened so split root stays
// top.
- dragLayer
+ layers.dragLayer
}
startTransaction.apply {
setLayer(change.leash, layer)
@@ -357,7 +342,7 @@
state.draggedTaskChange = change
val bounds = change.endAbsBounds
startTransaction.apply {
- setLayer(change.leash, dragLayer)
+ setLayer(change.leash, layers.dragLayer)
setWindowCrop(change.leash, bounds.width(), bounds.height())
show(change.leash)
}
@@ -370,7 +355,7 @@
state.otherRootChanges.add(change)
val bounds = change.endAbsBounds
startTransaction.apply {
- setLayer(change.leash, appLayers - i)
+ setLayer(change.leash, layers.appLayers - i)
setWindowCrop(change.leash, bounds.width(), bounds.height())
show(change.leash)
}
@@ -404,7 +389,7 @@
)
val bounds = change.endAbsBounds
startTransaction.apply {
- setLayer(change.leash, dragLayer)
+ setLayer(change.leash, layers.dragLayer)
setWindowCrop(change.leash, bounds.width(), bounds.height())
show(change.leash)
}
@@ -452,6 +437,15 @@
return true
}
+ /**
+ * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
+ * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
+ * the change index.
+ */
+ protected abstract fun calculateStartDragToDesktopLayers(
+ info: TransitionInfo
+ ): DragToDesktopLayers
+
override fun mergeAnimation(
transition: IBinder,
info: TransitionInfo,
@@ -483,104 +477,14 @@
state.startTransitionFinishCb
?: error("Start transition expected to be waiting for merge but wasn't")
if (isEndTransition) {
- info.changes.withIndex().forEach { (i, change) ->
- // If we're exiting split, hide the remaining split task.
- if (
- state is TransitionState.FromSplit &&
- change.taskInfo?.taskId == state.otherSplitTask
- ) {
- t.hide(change.leash)
- startTransactionFinishT.hide(change.leash)
- }
- if (change.mode == TRANSIT_CLOSE) {
- t.hide(change.leash)
- startTransactionFinishT.hide(change.leash)
- } else if (change.taskInfo?.taskId == state.draggedTaskId) {
- t.show(change.leash)
- startTransactionFinishT.show(change.leash)
- state.draggedTaskChange = change
- } else if (change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM) {
- // Other freeform tasks that are being restored go behind the dragged task.
- val draggedTaskLeash =
- state.draggedTaskChange?.leash
- ?: error("Expected dragged leash to be non-null")
- t.setRelativeLayer(change.leash, draggedTaskLeash, -i)
- startTransactionFinishT.setRelativeLayer(change.leash, draggedTaskLeash, -i)
- }
- }
-
- val draggedTaskChange =
- state.draggedTaskChange
- ?: throw IllegalStateException("Expected non-null change of dragged task")
- val draggedTaskLeash = draggedTaskChange.leash
- val startBounds = draggedTaskChange.startAbsBounds
- val endBounds = draggedTaskChange.endAbsBounds
-
- // Pause any animation that may be currently playing; we will use the relevant
- // details of that animation here.
- state.dragAnimator.cancelAnimator()
- // We still apply scale to task bounds; as we animate the bounds to their
- // end value, animate scale to 1.
- val startScale = state.dragAnimator.scale
- val startPosition = state.dragAnimator.position
- val unscaledStartWidth = startBounds.width()
- val unscaledStartHeight = startBounds.height()
- val unscaledStartBounds =
- Rect(
- startPosition.x.toInt(),
- startPosition.y.toInt(),
- startPosition.x.toInt() + unscaledStartWidth,
- startPosition.y.toInt() + unscaledStartHeight
- )
-
- dragToDesktopStateListener?.onCommitToDesktopAnimationStart(t)
- // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
- // and finish callback. Show the veil and position the task at the first frame before
- // starting the final animation.
- onTaskResizeAnimationListener.onAnimationStart(
- state.draggedTaskId,
- t,
- unscaledStartBounds
+ setupEndDragToDesktop(
+ info,
+ startTransaction = t,
+ finishTransaction = startTransactionFinishT
)
+ // Call finishCallback to merge animation before startTransitionFinishCb is called
finishCallback.onTransitionFinished(null /* wct */)
- val tx: SurfaceControl.Transaction = transactionSupplier.get()
- ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
- .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
- .apply {
- addUpdateListener { animator ->
- val animBounds = animator.animatedValue as Rect
- val animFraction = animator.animatedFraction
- // Progress scale from starting value to 1 as animation plays.
- val animScale = startScale + animFraction * (1 - startScale)
- tx.apply {
- setScale(draggedTaskLeash, animScale, animScale)
- setPosition(
- draggedTaskLeash,
- animBounds.left.toFloat(),
- animBounds.top.toFloat()
- )
- setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
- }
- onTaskResizeAnimationListener.onBoundsChange(
- state.draggedTaskId,
- tx,
- animBounds
- )
- }
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
- startTransitionFinishCb.onTransitionFinished(null /* null */)
- clearState()
- interactionJankMonitor.end(
- CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
- )
- }
- }
- )
- start()
- }
+ animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
} else if (isCancelTransition) {
info.changes.forEach { change ->
t.show(change.leash)
@@ -593,6 +497,122 @@
}
}
+ protected open fun setupEndDragToDesktop(
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ val state = requireTransitionState()
+ val freeformTaskChanges = mutableListOf<Change>()
+ info.changes.forEachIndexed { i, change ->
+ when {
+ state is TransitionState.FromSplit &&
+ change.taskInfo?.taskId == state.otherSplitTask -> {
+ // If we're exiting split, hide the remaining split task.
+ startTransaction.hide(change.leash)
+ finishTransaction.hide(change.leash)
+ }
+ change.mode == TRANSIT_CLOSE -> {
+ startTransaction.hide(change.leash)
+ finishTransaction.hide(change.leash)
+ }
+ change.taskInfo?.taskId == state.draggedTaskId -> {
+ startTransaction.show(change.leash)
+ finishTransaction.show(change.leash)
+ state.draggedTaskChange = change
+ }
+ change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM -> {
+ // Other freeform tasks that are being restored go behind the dragged task.
+ val draggedTaskLeash =
+ state.draggedTaskChange?.leash
+ ?: error("Expected dragged leash to be non-null")
+ startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+ finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i)
+ freeformTaskChanges.add(change)
+ }
+ }
+ }
+
+ state.freeformTaskChanges = freeformTaskChanges
+ }
+
+ protected open fun animateEndDragToDesktop(
+ startTransaction: SurfaceControl.Transaction,
+ startTransitionFinishCb: Transitions.TransitionFinishCallback
+ ) {
+ val state = requireTransitionState()
+ val draggedTaskChange =
+ state.draggedTaskChange ?: error("Expected non-null change of dragged task")
+ val draggedTaskLeash = draggedTaskChange.leash
+ val startBounds = draggedTaskChange.startAbsBounds
+ val endBounds = draggedTaskChange.endAbsBounds
+
+ // Cancel any animation that may be currently playing; we will use the relevant
+ // details of that animation here.
+ state.dragAnimator.cancelAnimator()
+ // We still apply scale to task bounds; as we animate the bounds to their
+ // end value, animate scale to 1.
+ val startScale = state.dragAnimator.scale
+ val startPosition = state.dragAnimator.position
+ val unscaledStartWidth = startBounds.width()
+ val unscaledStartHeight = startBounds.height()
+ val unscaledStartBounds =
+ Rect(
+ startPosition.x.toInt(),
+ startPosition.y.toInt(),
+ startPosition.x.toInt() + unscaledStartWidth,
+ startPosition.y.toInt() + unscaledStartHeight
+ )
+
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+ // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+ // and finish callback. Show the veil and position the task at the first frame before
+ // starting the final animation.
+ onTaskResizeAnimationListener.onAnimationStart(
+ state.draggedTaskId,
+ startTransaction,
+ unscaledStartBounds
+ )
+ val tx: SurfaceControl.Transaction = transactionSupplier.get()
+ ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds)
+ .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+ .apply {
+ addUpdateListener { animator ->
+ val animBounds = animator.animatedValue as Rect
+ val animFraction = animator.animatedFraction
+ // Progress scale from starting value to 1 as animation plays.
+ val animScale = startScale + animFraction * (1 - startScale)
+ tx.apply {
+ setScale(draggedTaskLeash, animScale, animScale)
+ setPosition(
+ draggedTaskLeash,
+ animBounds.left.toFloat(),
+ animBounds.top.toFloat()
+ )
+ setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height())
+ }
+ onTaskResizeAnimationListener.onBoundsChange(
+ state.draggedTaskId,
+ tx,
+ animBounds
+ )
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
+ startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+ clearState()
+ interactionJankMonitor.end(
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+ )
+ }
+ }
+ )
+ start()
+ }
+ }
+
override fun handleRequest(
transition: IBinder,
request: TransitionRequestInfo
@@ -707,11 +727,12 @@
wct.reorder(wc, true /* toTop */)
}
}
- val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling")
+ val homeWc =
+ state.homeChange?.container ?: error("Home task should be non-null before cancelling")
wct.restoreTransientOrder(homeWc)
}
- private fun clearState() {
+ protected fun clearState() {
transitionState = null
}
@@ -731,10 +752,21 @@
return splitScreenController.getTaskInfo(otherTaskPos)?.taskId
}
- private fun requireTransitionState(): TransitionState {
+ protected fun requireTransitionState(): TransitionState {
return transitionState ?: error("Expected non-null transition state")
}
+ /**
+ * Represents the layering (Z order) that will be given to any window based on its type during
+ * the "start" transition of the drag-to-desktop transition
+ */
+ protected data class DragToDesktopLayers(
+ val appLayers: Int,
+ val homeLayers: Int,
+ val wallpaperLayers: Int,
+ val dragLayer: Int,
+ )
+
interface DragToDesktopStateListener {
fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
@@ -748,8 +780,9 @@
abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
abstract var cancelTransitionToken: IBinder?
- abstract var homeToken: WindowContainerToken?
+ abstract var homeChange: Change?
abstract var draggedTaskChange: Change?
+ abstract var freeformTaskChanges: List<Change>
abstract var cancelState: CancelState
abstract var startAborted: Boolean
@@ -760,8 +793,9 @@
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
override var cancelTransitionToken: IBinder? = null,
- override var homeToken: WindowContainerToken? = null,
+ override var homeChange: Change? = null,
override var draggedTaskChange: Change? = null,
+ override var freeformTaskChanges: List<Change> = emptyList(),
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
var otherRootChanges: MutableList<Change> = mutableListOf()
@@ -774,8 +808,9 @@
override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
override var cancelTransitionToken: IBinder? = null,
- override var homeToken: WindowContainerToken? = null,
+ override var homeChange: Change? = null,
override var draggedTaskChange: Change? = null,
+ override var freeformTaskChanges: List<Change> = emptyList(),
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
var splitRootChange: Change? = null,
@@ -797,6 +832,210 @@
companion object {
/** The duration of the animation to commit or cancel the drag-to-desktop gesture. */
- private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+ internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L
+ }
+}
+
+/** Enables flagged rollout of the [SpringDragToDesktopTransitionHandler] */
+class DefaultDragToDesktopTransitionHandler
+@JvmOverloads
+constructor(
+ context: Context,
+ transitions: Transitions,
+ taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ interactionJankMonitor: InteractionJankMonitor,
+ transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
+ SurfaceControl.Transaction()
+ },
+) :
+ DragToDesktopTransitionHandler(
+ context,
+ transitions,
+ taskDisplayAreaOrganizer,
+ interactionJankMonitor,
+ transactionSupplier
+ ) {
+
+ /**
+ * @return layers in order:
+ * - appLayers - non-wallpaper, non-home tasks excluding the dragged task go at the bottom
+ * - homeLayers - home task on top of apps
+ * - wallpaperLayers - wallpaper on top of home
+ * - dragLayer - the dragged task on top of everything, there's only 1 dragged task
+ */
+ override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
+ DragToDesktopLayers(
+ appLayers = info.changes.size,
+ homeLayers = info.changes.size * 2,
+ wallpaperLayers = info.changes.size * 3,
+ dragLayer = info.changes.size * 3
+ )
+}
+
+/** Desktop transition handler with spring based animation for the end drag to desktop transition */
+class SpringDragToDesktopTransitionHandler
+@JvmOverloads
+constructor(
+ context: Context,
+ transitions: Transitions,
+ taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ interactionJankMonitor: InteractionJankMonitor,
+ transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
+ SurfaceControl.Transaction()
+ },
+) :
+ DragToDesktopTransitionHandler(
+ context,
+ transitions,
+ taskDisplayAreaOrganizer,
+ interactionJankMonitor,
+ transactionSupplier
+ ) {
+
+ private val positionSpringConfig =
+ PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_LOW,
+ SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ )
+
+ private val sizeSpringConfig =
+ PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+
+ /**
+ * @return layers in order:
+ * - appLayers - below everything z < 0, effectively hides the leash
+ * - homeLayers - home task on top of apps, z in 0..<size
+ * - wallpaperLayers - wallpaper on top of home, z in size..<size*2
+ * - dragLayer - the dragged task on top of everything, z == size*2
+ */
+ override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers =
+ DragToDesktopLayers(
+ appLayers = -1,
+ homeLayers = info.changes.size - 1,
+ wallpaperLayers = info.changes.size * 2 - 1,
+ dragLayer = info.changes.size * 2
+ )
+
+ override fun setupEndDragToDesktop(
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ super.setupEndDragToDesktop(info, startTransaction, finishTransaction)
+
+ val state = requireTransitionState()
+ val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null")
+ // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
+ finishTransaction.hide(homeLeash)
+ // Setup freeform tasks before animation
+ state.freeformTaskChanges.forEach { change ->
+ val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+ val startX =
+ change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2
+ val startY =
+ change.endAbsBounds.top + change.endAbsBounds.height() * (1 - startScale) / 2
+ startTransaction.setPosition(change.leash, startX, startY)
+ startTransaction.setScale(change.leash, startScale, startScale)
+ startTransaction.setAlpha(change.leash, 0f)
+ }
+ }
+
+ override fun animateEndDragToDesktop(
+ startTransaction: SurfaceControl.Transaction,
+ startTransitionFinishCb: Transitions.TransitionFinishCallback
+ ) {
+ val state = requireTransitionState()
+ val draggedTaskChange =
+ state.draggedTaskChange ?: error("Expected non-null change of dragged task")
+ val draggedTaskLeash = draggedTaskChange.leash
+ val freeformTaskChanges = state.freeformTaskChanges
+ val startBounds = draggedTaskChange.startAbsBounds
+ val endBounds = draggedTaskChange.endAbsBounds
+ val currentVelocity = state.dragAnimator.computeCurrentVelocity()
+
+ // Cancel any animation that may be currently playing; we will use the relevant
+ // details of that animation here.
+ state.dragAnimator.cancelAnimator()
+ // We still apply scale to task bounds; as we animate the bounds to their
+ // end value, animate scale to 1.
+ val startScale = state.dragAnimator.scale
+ val startPosition = state.dragAnimator.position
+ val startBoundsWithOffset =
+ Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }
+
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+ // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
+ // and finish callback. Show the veil and position the task at the first frame before
+ // starting the final animation.
+ onTaskResizeAnimationListener.onAnimationStart(
+ state.draggedTaskId,
+ startTransaction,
+ startBoundsWithOffset
+ )
+
+ val tx: SurfaceControl.Transaction = transactionSupplier.get()
+ PhysicsAnimator.getInstance(startBoundsWithOffset)
+ .spring(
+ FloatProperties.RECT_X,
+ endBounds.left.toFloat(),
+ currentVelocity.x,
+ positionSpringConfig
+ )
+ .spring(
+ FloatProperties.RECT_Y,
+ endBounds.top.toFloat(),
+ currentVelocity.y,
+ positionSpringConfig
+ )
+ .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig)
+ .addUpdateListener { animBounds, _ ->
+ val animFraction =
+ (animBounds.width() - startBounds.width()).toFloat() /
+ (endBounds.width() - startBounds.width())
+ val animScale = startScale + animFraction * (1 - startScale)
+ // Freeform animation starts 50% in the animation
+ val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f
+ val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE
+ val freeformAnimScale =
+ freeformStartScale + freeformAnimFraction * (1 - freeformStartScale)
+ tx.apply {
+ // Update dragged task
+ setScale(draggedTaskLeash, animScale, animScale)
+ setPosition(
+ draggedTaskLeash,
+ animBounds.left.toFloat(),
+ animBounds.top.toFloat()
+ )
+ // Update freeform tasks
+ freeformTaskChanges.forEach {
+ val startX =
+ it.endAbsBounds.left +
+ it.endAbsBounds.width() * (1 - freeformAnimScale) / 2
+ val startY =
+ it.endAbsBounds.top +
+ it.endAbsBounds.height() * (1 - freeformAnimScale) / 2
+ setPosition(it.leash, startX, startY)
+ setScale(it.leash, freeformAnimScale, freeformAnimScale)
+ setAlpha(it.leash, freeformAnimFraction)
+ }
+ }
+ onTaskResizeAnimationListener.onBoundsChange(state.draggedTaskId, tx, animBounds)
+ }
+ .withEndActions({
+ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
+ startTransitionFinishCb.onTransitionFinished(/* wct = */ null)
+ clearState()
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE)
+ })
+ .start()
+ }
+
+ companion object {
+ /**
+ * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop
+ * gesture.
+ */
+ private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index b01b2b7..afdda8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -5,3 +5,6 @@
pbdr@google.com
tkachenkoi@google.com
vaniadesmonda@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 8a0eea0..93351c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -5,3 +5,7 @@
nmusgrave@google.com
pbdr@google.com
tkachenkoi@google.com
+vaniadesmonda@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 723a531..428cc91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -693,16 +693,6 @@
return;
}
- if (mSplitScreenOptional.isPresent()) {
- // If pip activity will reparent to origin task case and if the origin task still
- // under split root, apply exit split transaction to make it expand to fullscreen.
- SplitScreenController split = mSplitScreenOptional.get();
- if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
- split.prepareExitSplitScreen(wct, split.getStageOfTask(
- mTaskInfo.lastParentTaskIdBeforePip),
- SplitScreenController.EXIT_REASON_APP_FINISHED);
- }
- }
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 284620e..da6221e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -632,6 +632,12 @@
public void insetsChanged(InsetsState insetsState) {
DisplayLayout pendingLayout = mDisplayController
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
+ if (pendingLayout == null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "insetsChanged: no display layout for displayId=%d",
+ mPipDisplayLayoutState.getDisplayId());
+ return;
+ }
if (mIsInFixedRotation
|| mIsKeyguardShowingOrAnimating
|| pendingLayout.rotation()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index c189642..0d7f7f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -34,11 +34,13 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -151,6 +153,10 @@
// How long the shell will wait for the app to close the PiP if a custom action is set.
private final int mPipForceCloseDelay;
+ // Context for the currently active user. This may differ from the regular systemui Context
+ // in cases such as secondary users or HSUM.
+ private Context mContextForUser;
+
public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler,
PipUiEventLogger pipUiEventLogger) {
@@ -202,6 +208,7 @@
.getInteger(R.integer.config_pipExitAnimationDuration);
initAccessibility();
+ setContextForUser();
}
private void initAccessibility() {
@@ -476,7 +483,7 @@
actionView.setImageDrawable(null);
} else {
// TODO: Check if the action drawable has changed before we reload it
- action.getIcon().loadDrawableAsync(mContext, d -> {
+ action.getIcon().loadDrawableAsync(mContextForUser, d -> {
if (d != null) {
d.setTint(Color.WHITE);
actionView.setImageDrawable(d);
@@ -510,6 +517,33 @@
expandContainer.requestLayout();
}
+ /**
+ * Sets the Context for the current user. If the user is the same as systemui, then simply
+ * use systemui Context.
+ */
+ private void setContextForUser() {
+ int userId = ActivityManager.getCurrentUser();
+
+ if (mContext.getUserId() != userId) {
+ try {
+ mContextForUser = mContext.createPackageContextAsUser(mContext.getPackageName(),
+ Context.CONTEXT_RESTRICTED, new UserHandle(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ // Shouldn't happen, use systemui context as backup
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to get context for user. Sysui userid=%d,"
+ + " current userid=%d, error=%s",
+ TAG,
+ mContext.getUserId(),
+ userId,
+ e);
+ mContextForUser = mContext;
+ }
+ } else {
+ mContextForUser = mContext;
+ }
+ }
+
private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
mController.onMenuStateChangeStart(menuState, resize, callback);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 7774384..dc21f82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -31,6 +31,8 @@
import android.os.Bundle;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;
@@ -40,6 +42,7 @@
import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -71,7 +74,8 @@
*/
public class PipController implements ConfigurationChangeListener,
PipTransitionState.PipTransitionStateChangedListener,
- DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
+ DisplayController.OnDisplaysChangedListener,
+ DisplayChangeController.OnDisplayChangingListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
@@ -197,11 +201,12 @@
mPipDisplayLayoutState.setDisplayLayout(layout);
mDisplayController.addDisplayWindowListener(this);
+ mDisplayController.addDisplayChangingController(this);
mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
- onDisplayChanged(mDisplayController
+ setDisplayLayout(mDisplayController
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
@@ -264,11 +269,12 @@
@Override
public void onThemeChanged() {
- onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+ setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
}
//
- // DisplayController.OnDisplaysChangedListener implementations
+ // DisplayController.OnDisplaysChangedListener and
+ // DisplayChangeController.OnDisplayChangingListener implementations
//
@Override
@@ -276,7 +282,7 @@
if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
- onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
}
@Override
@@ -284,10 +290,35 @@
if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
- onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+ setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
}
- private void onDisplayChanged(DisplayLayout layout) {
+ /**
+ * A callback for any observed transition that contains a display change in its
+ * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta.
+ */
+ @Override
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) {
+ if (!mPipTransitionState.isInPip()) {
+ return;
+ }
+
+ // Calculate the snap fraction pre-rotation.
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+
+ // Update the caches to reflect the new display layout and movement bounds.
+ mPipDisplayLayoutState.rotateTo(toRotation);
+ mPipTouchHandler.updateMovementBounds();
+
+ // The policy is to keep PiP width, height and snap fraction invariant.
+ Rect toBounds = mPipBoundsState.getBounds();
+ mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
+ mPipBoundsState.setBounds(toBounds);
+ t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
+ }
+
+ private void setDisplayLayout(DisplayLayout layout) {
mPipDisplayLayoutState.setDisplayLayout(layout);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index d7c225b..d75fa00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -1081,7 +1081,7 @@
* Updates the current movement bounds based on whether the menu is currently visible and
* resized.
*/
- private void updateMovementBounds() {
+ void updateMovementBounds() {
Rect insetBounds = new Rect();
mPipBoundsAlgorithm.getInsetBounds(insetBounds);
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 497c3f7..f739d65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -61,6 +61,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
"Bubbles"),
+ WM_SHELL_COMPAT_UI(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_COMPAT_UI),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
@@ -128,6 +130,7 @@
private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode";
+ private static final String TAG_WM_COMPAT_UI = "CompatUi";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 48d17ec6..c11a112 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -439,9 +439,9 @@
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
mPendingResize != null);
if (mPendingResize != null) {
+ mPendingResize.cancel(null);
mainDecor.cancelRunningAnimations();
sideDecor.cancelRunningAnimations();
- mPendingResize.cancel(null);
mAnimations.clear();
onFinish(null /* wct */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a7551bd..9bf5159 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -123,10 +123,10 @@
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.LaunchAdjacentController;
-import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -1010,40 +1010,41 @@
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
- topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
- bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
- mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
+
+ // Don't allow windows or divider to be focused during animation (mRootTaskInfo is the
+ // parent of all 3 leaves). We don't want the user to be able to tap and focus a window
+ // while it is moving across the screen, because granting focus also recalculates the
+ // layering order, which is in delicate balance during this animation.
+ WindowContainerTransaction noFocus = new WindowContainerTransaction();
+ noFocus.setFocusable(mRootTaskInfo.token, false);
+ mSyncQueue.queue(noFocus);
+
+ mSplitLayout.playSwapAnimation(t, topLeftStage, bottomRightStage,
insets -> {
+ // Runs at the end of the swap animation
+ SplitDecorManager decorManager1 = topLeftStage.getDecorManager();
+ SplitDecorManager decorManager2 = bottomRightStage.getDecorManager();
+
WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ // Restore focus-ability to the windows and divider
+ wct.setFocusable(mRootTaskInfo.token, true);
+
setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(st -> {
updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
- st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
- st.setPosition(bottomRightScreenshot, insets.left, insets.top);
- final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
- va.addUpdateListener(valueAnimator-> {
- final float progress = (float) valueAnimator.getAnimatedValue();
- t.setAlpha(topLeftScreenshot, progress);
- t.setAlpha(bottomRightScreenshot, progress);
- t.apply();
- });
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(
- @androidx.annotation.NonNull Animator animation) {
- t.remove(topLeftScreenshot);
- t.remove(bottomRightScreenshot);
- t.apply();
- mTransactionPool.release(t);
- }
- });
- va.start();
+ // updateSurfaceBounds(), above, officially puts the two apps in their new
+ // stages. Starting on the next frame, all calculations are made using the
+ // new layouts/insets. So any follow-up animations on the same leashes below
+ // should contain some cleanup/repositioning to prevent jank.
+
+ // Play follow-up animations if needed
+ decorManager1.fadeOutVeilAndCleanUp(st);
+ decorManager2.fadeOutVeilAndCleanUp(st);
});
});
@@ -2242,6 +2243,7 @@
final @WindowManager.TransitionType int type = request.getType();
final boolean isOpening = isOpeningType(type);
final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ final StageTaskListener stage = getStageOfTask(triggerTask);
if (isOpening && inFullscreen) {
// One task is opening into fullscreen mode, remove the corresponding split record.
@@ -2257,7 +2259,6 @@
+ " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
mMainStage.getChildCount(), mSideStage.getChildCount());
out = new WindowContainerTransaction();
- final StageTaskListener stage = getStageOfTask(triggerTask);
if (stage != null) {
if (isClosingType(type) && stage.getChildCount() == 1) {
// Dismiss split if the last task in one of the stages is going away
@@ -2330,16 +2331,8 @@
// Don't intercept the transition if we are not handling it as a part of one of the
// cases above and it is not already visible
return null;
- } else {
- if (triggerTask.parentTaskId == mMainStage.mRootTaskInfo.taskId
- || triggerTask.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
- + "restoring to split", request.getDebugId());
- out = new WindowContainerTransaction();
- mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
- }
- if (isOpening && getStageOfTask(triggerTask) != null) {
+ } else if (stage != null) {
+ if (isOpening) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split",
request.getDebugId());
// One task is appearing into split, prepare to enter split screen.
@@ -2347,9 +2340,15 @@
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
+ return out;
}
- return out;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
+ + "restoring to split", request.getDebugId());
+ out = new WindowContainerTransaction();
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
}
+ return out;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index d1ab3e9..f19eb3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -69,7 +69,7 @@
*
* @see StageCoordinator
*/
-class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = StageTaskListener.class.getSimpleName();
/** Callback interface for listening to changes in a split-screen stage. */
@@ -162,6 +162,18 @@
return getChildTaskInfo(predicate) != null;
}
+ public SurfaceControl getRootLeash() {
+ return mRootLeash;
+ }
+
+ public ActivityManager.RunningTaskInfo getRunningTaskInfo() {
+ return mRootTaskInfo;
+ }
+
+ public SplitDecorManager getDecorManager() {
+ return mSplitDecorManager;
+ }
+
@Nullable
private ActivityManager.RunningTaskInfo getChildTaskInfo(
Predicate<ActivityManager.RunningTaskInfo> predicate) {
@@ -335,7 +347,7 @@
void fadeOutDecor(Runnable finishedCallback) {
if (mSplitDecorManager != null) {
- mSplitDecorManager.fadeOutDecor(finishedCallback);
+ mSplitDecorManager.fadeOutDecor(finishedCallback, false /* addDelay */);
} else {
finishedCallback.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index f372557..1a38449 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.startingsurface;
-import static android.graphics.Color.WHITE;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import android.app.ActivityManager;
@@ -69,8 +68,9 @@
// Can't show splash screen on requested display, so skip showing at all.
return;
}
+ final int theme = getSplashScreenTheme(0 /* splashScreenThemeResId */, activityInfo);
final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
- 0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+ theme, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
if (myContext == null) {
return;
}
@@ -86,19 +86,11 @@
final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
lp.width = windowBounds.width();
lp.height = windowBounds.height();
- final ActivityManager.TaskDescription taskDescription;
- if (taskInfo.taskDescription != null) {
- taskDescription = taskInfo.taskDescription;
- } else {
- taskDescription = new ActivityManager.TaskDescription();
- taskDescription.setBackgroundColor(WHITE);
- }
final FrameLayout rootLayout = new FrameLayout(
mSplashscreenContentDrawer.createViewContextWrapper(myContext));
viewHost.setView(rootLayout, lp);
-
- final int bgColor = taskDescription.getBackgroundColor();
+ final int bgColor = mSplashscreenContentDrawer.estimateTaskBackgroundColor(myContext);
final SplashScreenView splashScreenView = mSplashscreenContentDrawer
.makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
rootLayout.addView(splashScreenView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 75e7ddf..a27c14bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -19,7 +19,9 @@
import static android.app.ActivityOptions.ANIM_FROM_STYLE;
import static android.app.ActivityOptions.ANIM_NONE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
@@ -221,6 +223,15 @@
*/
public static int getTransitionTypeFromInfo(@NonNull TransitionInfo info) {
final int type = info.getType();
+ // This back navigation is canceled, check whether the transition should be open or close
+ if (type == TRANSIT_PREPARE_BACK_NAVIGATION
+ || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) {
+ if (!info.getChanges().isEmpty()) {
+ final TransitionInfo.Change change = info.getChanges().get(0);
+ return TransitionUtil.isOpeningMode(change.getMode())
+ ? TRANSIT_OPEN : TRANSIT_CLOSE;
+ }
+ }
// If the info transition type is opening transition, iterate its changes to see if it
// has any opening change, if none, returns TRANSIT_CLOSE type for closing animation.
if (type == TRANSIT_OPEN) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a242b8a..8c8f205 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -69,7 +69,6 @@
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
-import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -115,6 +114,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -321,7 +321,7 @@
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
mShellCommandHandler.addDumpCallback(this::dump, this);
- mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+ mDisplayInsetsController.addGlobalInsetsChangedListener(
new DesktopModeOnInsetsChangedListener());
mDesktopTasksController.setOnTaskResizeAnimationListener(
new DesktopModeOnTaskResizeAnimationListener());
@@ -1196,10 +1196,6 @@
&& mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
return false;
}
- if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
- && taskInfo.isFocused) {
- return false;
- }
if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext)
&& isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
@@ -1397,19 +1393,17 @@
}
}
- static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
- private boolean mIsKeyguardVisible;
- private boolean mIsKeyguardOccluded;
-
+ class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
@Override
public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
boolean animatingDismiss) {
- mIsKeyguardVisible = visible;
- mIsKeyguardOccluded = occluded;
- }
-
- public boolean isKeyguardVisibleAndOccluded() {
- return mIsKeyguardVisible && mIsKeyguardOccluded;
+ final int size = mWindowDecorByTaskId.size();
+ for (int i = size - 1; i >= 0; i--) {
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ if (decor != null) {
+ decor.onKeyguardStateChanged(visible, occluded);
+ }
+ }
}
}
@@ -1417,28 +1411,26 @@
class DesktopModeOnInsetsChangedListener implements
DisplayInsetsController.OnInsetsChangedListener {
@Override
- public void insetsChanged(InsetsState insetsState) {
- for (int i = 0; i < insetsState.sourceSize(); i++) {
- final InsetsSource source = insetsState.sourceAt(i);
- if (source.getType() != statusBars()) {
+ public void insetsChanged(int displayId, @NonNull InsetsState insetsState) {
+ final int size = mWindowDecorByTaskId.size();
+ for (int i = size - 1; i >= 0; i--) {
+ final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ if (decor == null) {
continue;
}
-
- final DesktopModeWindowDecoration decor = getFocusedDecor();
- if (decor == null) {
- return;
+ if (decor.mTaskInfo.displayId == displayId
+ && Flags.enableDesktopWindowingImmersiveHandleHiding()) {
+ decor.onInsetsStateChanged(insetsState);
}
- // If status bar inset is visible, top task is not in immersive mode
- final boolean inImmersiveMode = !source.isVisible();
- // Calls WindowDecoration#relayout if decoration visibility needs to be updated
- if (inImmersiveMode != mInImmersiveMode) {
- if (Flags.enableDesktopWindowingImmersiveHandleHiding()) {
- decor.relayout(decor.mTaskInfo);
- }
- mInImmersiveMode = inImmersiveMode;
+ if (!Flags.enableAdditionalWindowsAboveStatusBar()) {
+ // If status bar inset is visible, top task is not in immersive mode.
+ // This value is only needed when the App Handle input is being handled
+ // through the global input monitor (hence the flag check) to ignore gestures
+ // when the app is in immersive mode. When disabled, the view itself handles
+ // input, and since it's removed when in immersive there's no need to track
+ // this here.
+ mInImmersiveMode = !InsetsStateKt.isVisible(insetsState, statusBars());
}
-
- return;
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 54b33e93..095d337 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -60,6 +60,7 @@
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
@@ -120,8 +121,9 @@
/** Closes the maximize window and releases its view. */
fun close() {
- maximizeMenuView?.cancelAnimation()
- maximizeMenu?.releaseView()
+ maximizeMenuView?.animateCloseMenu {
+ maximizeMenu?.releaseView()
+ }
maximizeMenu = null
maximizeMenuView = null
}
@@ -255,7 +257,7 @@
.getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius)
private val hoverTempRect = Rect()
- private val openMenuAnimatorSet = AnimatorSet()
+ private var menuAnimatorSet: AnimatorSet? = null
private lateinit var taskInfo: RunningTaskInfo
private lateinit var style: MenuStyle
@@ -346,15 +348,16 @@
fun animateOpenMenu() {
maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
- openMenuAnimatorSet.playTogether(
+ menuAnimatorSet = AnimatorSet()
+ menuAnimatorSet?.playTogether(
ObjectAnimator.ofFloat(rootView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f)
.apply {
- duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
interpolator = EMPHASIZED_DECELERATE
},
ValueAnimator.ofFloat(STARTING_MENU_HEIGHT_SCALE, 1f)
.apply {
- duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
interpolator = EMPHASIZED_DECELERATE
addUpdateListener {
// Animate padding so that controls stay pinned to the bottom of
@@ -367,7 +370,7 @@
}
},
ValueAnimator.ofFloat(1 / STARTING_MENU_HEIGHT_SCALE, 1f).apply {
- duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
interpolator = EMPHASIZED_DECELERATE
addUpdateListener {
// Scale up the children of the maximize menu so that the menu
@@ -381,7 +384,7 @@
},
ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
(STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply {
- duration = MENU_HEIGHT_ANIMATION_DURATION_MS
+ duration = OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS
interpolator = EMPHASIZED_DECELERATE
},
ObjectAnimator.ofInt(rootView.background, "alpha",
@@ -391,7 +394,7 @@
ValueAnimator.ofFloat(0f, 1f)
.apply {
duration = ALPHA_ANIMATION_DURATION_MS
- startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+ startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
addUpdateListener {
val value = animatedValue as Float
maximizeButton.alpha = value
@@ -403,21 +406,96 @@
ObjectAnimator.ofFloat(rootView, TRANSLATION_Z, MENU_Z_TRANSLATION)
.apply {
duration = ELEVATION_ANIMATION_DURATION_MS
- startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS
+ startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS
}
)
- openMenuAnimatorSet.addListener(
+ menuAnimatorSet?.addListener(
onEnd = {
maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
}
)
- openMenuAnimatorSet.start()
+ menuAnimatorSet?.start()
}
- /** Cancel the open menu animation. */
- fun cancelAnimation() {
- openMenuAnimatorSet.cancel()
+ /** Animate the closing of the menu */
+ fun animateCloseMenu(onEnd: (() -> Unit)) {
+ maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ cancelAnimation()
+ menuAnimatorSet = AnimatorSet()
+ menuAnimatorSet?.playTogether(
+ ObjectAnimator.ofFloat(rootView, SCALE_Y, 1f, STARTING_MENU_HEIGHT_SCALE)
+ .apply {
+ duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = FAST_OUT_LINEAR_IN
+ },
+ ValueAnimator.ofFloat(1f, STARTING_MENU_HEIGHT_SCALE)
+ .apply {
+ duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = FAST_OUT_LINEAR_IN
+ addUpdateListener {
+ // Animate padding so that controls stay pinned to the bottom of
+ // the menu.
+ val value = animatedValue as Float
+ val topPadding = menuPadding -
+ ((1 - value) * menuHeight).toInt()
+ container.setPadding(menuPadding, topPadding,
+ menuPadding, menuPadding)
+ }
+ },
+ ValueAnimator.ofFloat(1f, 1 / STARTING_MENU_HEIGHT_SCALE).apply {
+ duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = FAST_OUT_LINEAR_IN
+ addUpdateListener {
+ // Scale up the children of the maximize menu so that the menu
+ // scale is cancelled out and only the background is scaled.
+ val value = animatedValue as Float
+ maximizeButton.scaleY = value
+ snapButtonsLayout.scaleY = value
+ maximizeText.scaleY = value
+ snapWindowText.scaleY = value
+ }
+ },
+ ObjectAnimator.ofFloat(rootView, TRANSLATION_Y,
+ 0f, (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight).apply {
+ duration = CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS
+ interpolator = FAST_OUT_LINEAR_IN
+ },
+ ObjectAnimator.ofInt(rootView.background, "alpha",
+ MAX_DRAWABLE_ALPHA_VALUE, 0).apply {
+ startDelay = CONTAINER_ALPHA_CLOSE_MENU_ANIMATION_DELAY_MS
+ duration = ALPHA_ANIMATION_DURATION_MS
+ },
+ ValueAnimator.ofFloat(1f, 0f)
+ .apply {
+ duration = ALPHA_ANIMATION_DURATION_MS
+ addUpdateListener {
+ val value = animatedValue as Float
+ maximizeButton.alpha = value
+ snapButtonsLayout.alpha = value
+ maximizeText.alpha = value
+ snapWindowText.alpha = value
+ }
+ },
+ ObjectAnimator.ofFloat(rootView, TRANSLATION_Z, MENU_Z_TRANSLATION, 0f)
+ .apply {
+ duration = ELEVATION_ANIMATION_DURATION_MS
+ }
+ )
+ menuAnimatorSet?.addListener(
+ onEnd = {
+ maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ onEnd?.invoke()
+ }
+ )
+ menuAnimatorSet?.start()
+ }
+
+ /** Cancel the menu animation. */
+ private fun cancelAnimation() {
+ menuAnimatorSet?.cancel()
}
/** Update the view state to a new snap to half selection. */
@@ -645,9 +723,11 @@
private const val ALPHA_ANIMATION_DURATION_MS = 50L
private const val MAX_DRAWABLE_ALPHA_VALUE = 255
private const val STARTING_MENU_HEIGHT_SCALE = 0.8f
- private const val MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+ private const val OPEN_MENU_HEIGHT_ANIMATION_DURATION_MS = 300L
+ private const val CLOSE_MENU_HEIGHT_ANIMATION_DURATION_MS = 200L
private const val ELEVATION_ANIMATION_DURATION_MS = 50L
- private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
+ private const val CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS = 33L
+ private const val CONTAINER_ALPHA_CLOSE_MENU_ANIMATION_DELAY_MS = 33L
private const val MENU_Z_TRANSLATION = 1f
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index 9741667..70c0b54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -7,6 +7,7 @@
import android.graphics.Rect
import android.view.MotionEvent
import android.view.SurfaceControl
+import android.view.VelocityTracker
import com.android.wm.shell.R
/**
@@ -34,6 +35,7 @@
val scale: Float
get() = dragToDesktopAnimator.animatedValue as Float
private val mostRecentInput = PointF()
+ private val velocityTracker = VelocityTracker.obtain()
private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
DRAG_FREEFORM_SCALE)
.setDuration(ANIMATION_DURATION.toLong())
@@ -90,6 +92,7 @@
if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) {
return
}
+ velocityTracker.addMovement(ev)
setTaskPosition(ev.rawX, ev.rawY)
val t = transactionFactory()
t.setPosition(taskSurface, position.x, position.y)
@@ -109,6 +112,15 @@
* Cancels the animation, intended to be used when another animator will take over.
*/
fun cancelAnimator() {
+ velocityTracker.clear()
dragToDesktopAnimator.cancel()
}
+
+ /**
+ * Computes the current velocity per second based on the points that have been collected.
+ */
+ fun computeCurrentVelocity(): PointF {
+ velocityTracker.computeCurrentVelocity(/* units = */ 1000)
+ return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity)
+ }
}
\ No newline at end of file
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 0c58987..4af5b2c 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
@@ -61,6 +61,7 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import java.util.ArrayList;
import java.util.Arrays;
@@ -143,6 +144,9 @@
TaskDragResizer mTaskDragResizer;
boolean mIsCaptionVisible;
+ private boolean mIsStatusBarVisible;
+ private boolean mIsKeyguardVisibleAndOccluded;
+
/** The most recent set of insets applied to this window decoration. */
private WindowDecorationInsets mWindowDecorationInsets;
private final Binder mOwner = new Binder();
@@ -184,6 +188,9 @@
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
+ mIsStatusBarVisible = insetsState != null
+ && InsetsStateKt.isVisible(insetsState, statusBars());
}
/**
@@ -234,7 +241,7 @@
}
rootView = null; // Clear it just in case we use it accidentally
- updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+ updateCaptionVisibility(outResult.mRootView);
final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
outResult.mWidth = taskBounds.width();
@@ -284,17 +291,20 @@
mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
mDecorWindowContext.setTheme(mContext.getThemeResId());
if (params.mLayoutResId != 0) {
- outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
- .inflate(params.mLayoutResId, null);
+ outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
}
}
if (outResult.mRootView == null) {
- outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
- .inflate(params.mLayoutResId, null);
+ outResult.mRootView = inflateLayout(mDecorWindowContext, params.mLayoutResId);
}
}
+ @VisibleForTesting
+ T inflateLayout(Context context, int layoutResId) {
+ return (T) LayoutInflater.from(context).inflate(layoutResId, null);
+ }
+
private void updateDecorationContainerSurface(
SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
if (mDecorationContainerSurface == null) {
@@ -497,22 +507,31 @@
throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
}
+ void onKeyguardStateChanged(boolean visible, boolean occluded) {
+ final boolean prevVisAndOccluded = mIsKeyguardVisibleAndOccluded;
+ mIsKeyguardVisibleAndOccluded = visible && occluded;
+ final boolean changed = prevVisAndOccluded != mIsKeyguardVisibleAndOccluded;
+ if (changed) {
+ relayout(mTaskInfo);
+ }
+ }
+
+ void onInsetsStateChanged(@NonNull InsetsState insetsState) {
+ final boolean prevStatusBarVisibility = mIsStatusBarVisible;
+ mIsStatusBarVisible = InsetsStateKt.isVisible(insetsState, statusBars());
+ final boolean changed = prevStatusBarVisibility != mIsStatusBarVisible;
+
+ if (changed) {
+ relayout(mTaskInfo);
+ }
+ }
+
/**
* Checks if task has entered/exited immersive mode and requires a change in caption visibility.
*/
- private void updateCaptionVisibility(View rootView, int displayId) {
- final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
- for (int i = 0; i < insetsState.sourceSize(); i++) {
- final InsetsSource source = insetsState.sourceAt(i);
- if (source.getType() != statusBars()) {
- continue;
- }
-
- mIsCaptionVisible = source.isVisible();
- setCaptionVisibility(rootView, mIsCaptionVisible);
-
- return;
- }
+ private void updateCaptionVisibility(View rootView) {
+ mIsCaptionVisible = mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded;
+ setCaptionVisibility(rootView, mIsCaptionVisible);
}
void setTaskDragResizer(TaskDragResizer taskDragResizer) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt
new file mode 100644
index 0000000..be01a20
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/InsetsState.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.extension
+
+import android.view.InsetsState
+import android.view.WindowInsets
+
+/**
+ * Whether the source of the given [type] is visible or false if there is no source of that type.
+ */
+fun InsetsState.isVisible(@WindowInsets.Type.InsetsType type: Int): Boolean {
+ for (i in 0 until sourceSize()) {
+ val source = sourceAt(i)
+ if (source.type != type) {
+ continue
+ }
+ return source.isVisible
+ }
+ return false
+}
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 9f735c4..d1be12f 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -15,3 +15,6 @@
mpodolian@google.com
jeremysim@google.com
peanutbutter@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
similarity index 63%
copy from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
copy to libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
index cd7a879..079ee13 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2024 The Android Open Source Project
~
@@ -13,10 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.overlay.hsumconfig"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
-</manifest>
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/caption"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:background="@drawable/caption_decor_title"/>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 669e433..9df9956 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -18,6 +18,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -160,6 +161,19 @@
assertTrue(secondListener.hideInsetsCount == 1);
}
+ @Test
+ public void testGlobalListenerCallback() throws RemoteException {
+ TrackedListener globalListener = new TrackedListener();
+ addDisplay(SECOND_DISPLAY);
+ mController.addGlobalInsetsChangedListener(globalListener);
+
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
+ mExecutor.flushAll();
+
+ assertEquals(2, globalListener.insetsChangedCount);
+ }
+
private void addDisplay(int displayId) throws RemoteException {
mController.onDisplayAdded(displayId);
verify(mWm, times(mInsetsControllersByDisplayId.size() + 1))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index de1659b..b39cf19 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -43,6 +43,7 @@
import android.view.InsetsState;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
@@ -128,6 +129,9 @@
@Captor
ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
+ @NonNull
+ private CompatUIStatusManager mCompatUIStatusManager;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -147,11 +151,13 @@
doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
+ mCompatUIStatusManager = new CompatUIStatusManager();
mShellInit = spy(new ShellInit(mMockExecutor));
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
- mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
+ mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
+ mCompatUIStatusManager) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
new file mode 100644
index 0000000..d6059a8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.compatui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
+
+/**
+ * Tests for {@link CompatUILayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:CompatUIStatusManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class CompatUIStatusManagerTest extends ShellTestCase {
+
+ private FakeCompatUIStatusManagerTest mTestState;
+ private CompatUIStatusManager mStatusManager;
+
+ @Before
+ public void setUp() {
+ mTestState = new FakeCompatUIStatusManagerTest();
+ mStatusManager = new CompatUIStatusManager(mTestState.mWriter, mTestState.mReader);
+ }
+
+ @Test
+ public void isEducationShown() {
+ assertFalse(mStatusManager.isEducationVisible());
+
+ mStatusManager.onEducationShown();
+ assertTrue(mStatusManager.isEducationVisible());
+
+ mStatusManager.onEducationHidden();
+ assertFalse(mStatusManager.isEducationVisible());
+ }
+
+ static class FakeCompatUIStatusManagerTest {
+
+ int mCurrentStatus = 0;
+
+ final IntSupplier mReader = () -> mCurrentStatus;
+
+ final IntConsumer mWriter = newStatus -> mCurrentStatus = newStatus;
+
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 7617269..94dbd11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -20,9 +20,13 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_VISIBLE;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertEquals;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -38,6 +42,7 @@
import android.app.TaskInfo;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -54,6 +59,7 @@
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -61,6 +67,7 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIStatusManagerTest.FakeCompatUIStatusManagerTest;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
@@ -120,6 +127,8 @@
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
+ private FakeCompatUIStatusManagerTest mCompatUIStatus;
+ private CompatUIStatusManager mCompatUIStatusManager;
@Rule
public final CheckFlagsRule mCheckFlagsRule =
@@ -129,6 +138,9 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mExecutor = new TestShellExecutor();
+ mCompatUIStatus = new FakeCompatUIStatusManagerTest();
+ mCompatUIStatusManager = new CompatUIStatusManager(mCompatUIStatus.mWriter,
+ mCompatUIStatus.mReader);
mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
final Set<Integer> mHasSeenSet = new HashSet<>();
@@ -414,6 +426,21 @@
assertFalse(windowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS)
+ public void testCompatUIStatus_dialogIsShown() {
+ // We display the dialog
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_1, /* isTaskbarEduShowing= */ false);
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ assertNotNull(windowManager.mLayout);
+ assertEquals(/* expected= */ COMPAT_UI_EDUCATION_VISIBLE, mCompatUIStatus.mCurrentStatus);
+
+ // We dismiss
+ windowManager.release();
+ assertEquals(/* expected= */ COMPAT_UI_EDUCATION_HIDDEN, mCompatUIStatus.mCurrentStatus);
+ }
+
private void verifyLayout(LetterboxEduDialogLayout layout, ViewGroup.LayoutParams params,
int expectedWidth, int expectedHeight, int expectedExtraTopMargin,
int expectedExtraBottomMargin) {
@@ -464,7 +491,7 @@
windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController,
- mDockStateReader, mCompatUIConfiguration);
+ mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 92f7050..7bb5449 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -731,6 +731,64 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ // Add freeform task with half display size snap bounds at left side.
+ setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ // Add freeform task with half display size snap bounds at right side.
+ setUpFreeformTask(bounds = Rect(
+ stableBounds.right - 500, stableBounds.top, stableBounds.right, stableBounds.bottom))
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ // Add maximised freeform task.
+ setUpFreeformTask(bounds = Rect(stableBounds))
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
fun addMoveToDesktopChanges_defaultToCenterIfFree() {
setUpLandscapeDisplay()
val stableBounds = Rect()
@@ -751,6 +809,50 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+ setUpPortraitDisplay()
+ val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ setUpPortraitDisplay()
+ val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -1305,13 +1407,36 @@
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
freeformTasks.forEach { markTaskVisible(it) }
val fullscreenTask = createFullscreenTask()
+ val homeTask = setUpHomeTask(DEFAULT_DISPLAY)
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
// Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(3)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(1, freeformTasks[0], toTop = false)
+ wct.assertReorderAt(1, homeTask, toTop = false)
+ wct.assertReorderAt(2, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskToFreeform_alreadyBeyondLimit_existingAndNewTasksAreMinimized() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ val minimizedTask = setUpFreeformTask()
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val homeTask = setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(4)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ // Make sure we reorder the home task to the bottom, and minimized tasks below the home task.
+ wct.assertReorderAt(1, homeTask, toTop = false)
+ wct.assertReorderAt(2, minimizedTask, toTop = false)
+ wct.assertReorderAt(3, freeformTasks[0], toTop = false)
}
@Test
@@ -2712,13 +2837,15 @@
}
private fun setUpFullscreenTask(
- displayId: Int = DEFAULT_DISPLAY,
- isResizable: Boolean = true,
- windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
- deviceOrientation: Int = ORIENTATION_LANDSCAPE,
- screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
- shouldLetterbox: Boolean = false,
- gravity: Int = Gravity.NO_GRAVITY
+ displayId: Int = DEFAULT_DISPLAY,
+ isResizable: Boolean = true,
+ windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+ deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+ screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+ shouldLetterbox: Boolean = false,
+ gravity: Int = Gravity.NO_GRAVITY,
+ enableUserFullscreenOverride: Boolean = false,
+ enableSystemFullscreenOverride: Boolean = false
): RunningTaskInfo {
val task = createFullscreenTask(displayId)
val activityInfo = ActivityInfo()
@@ -2729,6 +2856,8 @@
isResizeable = isResizable
configuration.orientation = deviceOrientation
configuration.windowConfiguration.windowingMode = windowingMode
+ appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
+ appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
if (shouldLetterbox) {
if (deviceOrientation == ORIENTATION_LANDSCAPE &&
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index e4e2bd2..c97bcfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -11,6 +11,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_OPEN
import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import android.window.WindowContainerTransaction
@@ -27,6 +28,7 @@
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import java.util.function.Supplier
import junit.framework.Assert.assertFalse
import org.junit.Before
import org.junit.Test
@@ -40,7 +42,6 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
-import java.util.function.Supplier
/** Tests of [DragToDesktopTransitionHandler]. */
@SmallTest
@@ -52,17 +53,26 @@
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var splitScreenController: SplitScreenController
@Mock private lateinit var dragAnimator: MoveToDesktopAnimator
- @Mock
- private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var draggedTaskLeash: SurfaceControl
+ @Mock private lateinit var homeTaskLeash: SurfaceControl
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
- private lateinit var handler: DragToDesktopTransitionHandler
+ private lateinit var defaultHandler: DragToDesktopTransitionHandler
+ private lateinit var springHandler: SpringDragToDesktopTransitionHandler
@Before
fun setUp() {
- handler =
- DragToDesktopTransitionHandler(
+ defaultHandler = DefaultDragToDesktopTransitionHandler(
+ context,
+ transitions,
+ taskDisplayAreaOrganizer,
+ mockInteractionJankMonitor,
+ transactionSupplier,
+ )
+ .apply { setSplitScreenController(splitScreenController) }
+ springHandler = SpringDragToDesktopTransitionHandler(
context,
transitions,
taskDisplayAreaOrganizer,
@@ -76,10 +86,10 @@
fun startDragToDesktop_animateDragWhenReady() {
val task = createTask()
// Simulate transition is started.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// Now it's ready to animate.
- handler.startAnimation(
+ defaultHandler.startAnimation(
transition = transition,
info =
createTransitionInfo(
@@ -96,65 +106,70 @@
@Test
fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() {
- performEarlyCancel(DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
+ )
verify(transitions)
- .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler))
+ .startTransition(
+ eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+ any(),
+ eq(defaultHandler)
+ )
}
@Test
fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() {
- performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT)
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT),
- any()
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
}
@Test
fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() {
- performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT)
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
- any()
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
}
@Test
fun startDragToDesktop_aborted_finishDropped() {
val task = createTask()
// Simulate transition is started.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// But the transition was aborted.
- handler.onTransitionConsumed(transition, aborted = true, mock())
+ defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
// Attempt to finish the failed drag start.
- handler.finishDragToDesktopTransition(WindowContainerTransaction())
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
// Should not be attempted and state should be reset.
verify(transitions, never())
- .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
- assertFalse(handler.inProgress)
+ .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
+ assertFalse(defaultHandler.inProgress)
}
@Test
fun startDragToDesktop_aborted_cancelDropped() {
val task = createTask()
// Simulate transition is started.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// But the transition was aborted.
- handler.onTransitionConsumed(transition, aborted = true, mock())
+ defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
// Attempt to finish the failed drag start.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
// Should not be attempted and state should be reset.
- assertFalse(handler.inProgress)
+ assertFalse(defaultHandler.inProgress)
}
@Test
@@ -162,23 +177,24 @@
val task = createTask()
// Simulate attempt to start two drag to desktop transitions.
- startDragToDesktopTransition(task, dragAnimator)
- startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(defaultHandler, task, dragAnimator)
+ startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// Verify transition only started once.
- verify(transitions, times(1)).startTransition(
+ verify(transitions, times(1))
+ .startTransition(
eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
any(),
- eq(handler)
- )
+ eq(defaultHandler)
+ )
}
@Test
fun cancelDragToDesktop_startWasReady_cancel() {
- startDrag()
+ startDrag(defaultHandler)
// Then user cancelled after it had already started.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
@@ -188,48 +204,40 @@
@Test
fun cancelDragToDesktop_splitLeftCancelType_splitRequested() {
- startDrag()
+ startDrag(defaultHandler)
// Then user cancelled it, requesting split.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
)
// Verify the request went through split controller.
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT),
- any()
- )
+ verify(splitScreenController)
+ .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
}
@Test
fun cancelDragToDesktop_splitRightCancelType_splitRequested() {
- startDrag()
+ startDrag(defaultHandler)
// Then user cancelled it, requesting split.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
)
// Verify the request went through split controller.
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
- any()
- )
+ verify(splitScreenController)
+ .requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
}
@Test
fun cancelDragToDesktop_startWasNotReady_animateCancel() {
val task = createTask()
// Simulate transition is started and is ready to animate.
- startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// Then user cancelled before the transition was ready and animated.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
@@ -240,50 +248,139 @@
@Test
fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
// Then cancel is called before the transition was started.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
// Verify cancel is dropped.
- verify(transitions, never()).startTransition(
+ verify(transitions, never())
+ .startTransition(
eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
any(),
- eq(handler)
- )
+ eq(defaultHandler)
+ )
}
@Test
fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
// Then finish is called before the transition was started.
- handler.finishDragToDesktopTransition(WindowContainerTransaction())
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
// Verify finish is dropped.
- verify(transitions, never()).startTransition(
+ verify(transitions, never())
+ .startTransition(
eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
any(),
- eq(handler)
- )
+ eq(defaultHandler)
+ )
}
- private fun startDrag() {
+ @Test
+ fun mergeAnimation_otherTransition_doesNotMerge() {
+ val transaction = mock<SurfaceControl.Transaction>()
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
+
+ startDrag(defaultHandler, task)
+ defaultHandler.mergeAnimation(
+ transition = mock(),
+ info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
+ t = transaction,
+ mergeTarget = mock(),
+ finishCallback = finishCallback
+ )
+
+ // Should NOT have any transaction changes
+ verifyZeroInteractions(transaction)
+ // Should NOT merge animation
+ verify(finishCallback, never()).onTransitionFinished(any())
+ }
+
+ @Test
+ fun mergeAnimation_endTransition_mergesAnimation() {
+ val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+ val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ val startTransition =
+ startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction)
+ defaultHandler.onTaskResizeAnimationListener = mock()
+
+ defaultHandler.mergeAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
+ t = mergedStartTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback
+ )
+
+ // Should show dragged task layer in start and finish transaction
+ verify(mergedStartTransaction).show(draggedTaskLeash)
+ verify(playingFinishTransaction).show(draggedTaskLeash)
+ // Should merge animation
+ verify(finishCallback).onTransitionFinished(null)
+ }
+
+ @Test
+ fun mergeAnimation_endTransition_springHandler_hidesHome() {
+ whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
+ val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+ val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ val startTransition =
+ startDrag(springHandler, task, finishTransaction = playingFinishTransaction)
+ springHandler.onTaskResizeAnimationListener = mock()
+
+ springHandler.mergeAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
+ t = mergedStartTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback
+ )
+
+ // Should show dragged task layer in start and finish transaction
+ verify(mergedStartTransaction).show(draggedTaskLeash)
+ verify(playingFinishTransaction).show(draggedTaskLeash)
+ // Should hide home task leash in finish transaction
+ verify(playingFinishTransaction).hide(homeTaskLeash)
+ // Should merge animation
+ verify(finishCallback).onTransitionFinished(null)
+ }
+
+ private fun startDrag(
+ handler: DragToDesktopTransitionHandler,
+ task: RunningTaskInfo = createTask(),
+ finishTransaction: SurfaceControl.Transaction = mock()
+ ): IBinder {
whenever(dragAnimator.position).thenReturn(PointF())
// Simulate transition is started and is ready to animate.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(handler, task, dragAnimator)
handler.startAnimation(
transition = transition,
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
- ),
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
startTransaction = mock(),
- finishTransaction = mock(),
+ finishTransaction = finishTransaction,
finishCallback = {}
)
+ return transition
}
private fun startDragToDesktopTransition(
+ handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo,
dragAnimator: MoveToDesktopAnimator
): IBinder {
@@ -300,20 +397,23 @@
return token
}
- private fun performEarlyCancel(cancelState: DragToDesktopTransitionHandler.CancelState) {
+ private fun performEarlyCancel(
+ handler: DragToDesktopTransitionHandler,
+ cancelState: DragToDesktopTransitionHandler.CancelState
+ ) {
val task = createTask()
// Simulate transition is started and is ready to animate.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(handler, task, dragAnimator)
handler.cancelDragToDesktopTransition(cancelState)
handler.startAnimation(
transition = transition,
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
- ),
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task
+ ),
startTransaction = mock(),
finishTransaction = mock(),
finishCallback = {}
@@ -340,7 +440,7 @@
private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo {
return TransitionInfo(type, 0 /* flags */).apply {
addChange( // Home.
- TransitionInfo.Change(mock(), mock()).apply {
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
parent = null
taskInfo =
TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
@@ -348,7 +448,7 @@
}
)
addChange( // Dragged Task.
- TransitionInfo.Change(mock(), mock()).apply {
+ TransitionInfo.Change(mock(), draggedTaskLeash).apply {
parent = null
taskInfo = draggedTask
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
index dd19d76..571bdd4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt
@@ -33,7 +33,7 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
+import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,9 +49,9 @@
@JvmField @Rule val setFlagsRule = SetFlagsRule()
- @Before
- fun setUp() {
- resetCache()
+ @After
+ fun tearDown() {
+ resetToggleOverrideCache()
}
// TODO(b/348193756): Add tests
@@ -338,7 +338,7 @@
}
}
- private fun resetCache() {
+ private fun resetToggleOverrideCache() {
val cachedToggleOverride =
DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride")
cachedToggleOverride.isAccessible = true
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index ee9f886..af6c077 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -370,6 +370,6 @@
Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets */,
false, true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
0 /* systemUiVisibility */, false /* isTranslucent */,
- hasImeSurface /* hasImeSurface */);
+ hasImeSurface /* hasImeSurface */, 0 /* uiMode */);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 68975ec..6d68797 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -36,7 +36,6 @@
import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.platform.test.flag.junit.SetFlagsRule
@@ -56,7 +55,6 @@
import android.view.SurfaceControl
import android.view.SurfaceView
import android.view.View
-import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
@@ -85,11 +83,11 @@
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
-import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
import java.util.Optional
import java.util.function.Consumer
@@ -172,6 +170,7 @@
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
private lateinit var displayChangingListener: DisplayChangeController.OnDisplayChangingListener
+ private lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
@Before
@@ -225,17 +224,20 @@
shellInit.init()
- val insetListenerCaptor =
- argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
- verify(displayInsetsController)
- .addInsetsChangedListener(anyInt(), insetListenerCaptor.capture())
- desktopModeOnInsetsChangedListener = insetListenerCaptor.firstValue
-
val displayChangingListenerCaptor =
argumentCaptor<DisplayChangeController.OnDisplayChangingListener>()
verify(mockDisplayController)
.addDisplayChangingController(displayChangingListenerCaptor.capture())
displayChangingListener = displayChangingListenerCaptor.firstValue
+ val insetsChangedCaptor =
+ argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+ verify(displayInsetsController)
+ .addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
+ desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+ val keyguardChangedCaptor =
+ argumentCaptor<DesktopModeKeyguardChangeListener>()
+ verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
+ desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
}
@After
@@ -354,26 +356,6 @@
}
@Test
- fun testCaptionIsNotCreatedWhenKeyguardIsVisible() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- val keyguardListenerCaptor = argumentCaptor<KeyguardChangeListener>()
- verify(mockShellController).addKeyguardChangeListener(keyguardListenerCaptor.capture())
-
- keyguardListenerCaptor.firstValue.onKeyguardVisibilityChanged(
- true /* visible */,
- true /* occluded */,
- false /* animatingDismiss */
- )
- onTaskOpening(task)
-
- task.setWindowingMode(WINDOWING_MODE_UNDEFINED)
- task.setWindowingMode(ACTIVITY_TYPE_UNDEFINED)
- onTaskChanging(task)
-
- assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
@@ -418,67 +400,50 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
- fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
- val decoration = setUpMockDecorationForTask(task)
-
- onTaskOpening(task)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
+ fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
+ val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
+ val decoration1 = setUpMockDecorationForTask(task1)
+ onTaskOpening(task1)
+ val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 2)
+ val decoration2 = setUpMockDecorationForTask(task2)
+ onTaskOpening(task2)
+ val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 2)
+ val decoration3 = setUpMockDecorationForTask(task3)
+ onTaskOpening(task3)
// Add status bar insets source
- val insetsState = InsetsState()
- val statusBarInsetsSourceId = 0
- val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
- statusBarInsetsSource.isVisible = false
- insetsState.addSource(statusBarInsetsSource)
+ val insetsState = InsetsState().apply {
+ addSource(InsetsSource(0 /* id */, statusBars()).apply {
+ isVisible = false
+ })
+ }
+ desktopModeOnInsetsChangedListener.insetsChanged(2 /* displayId */, insetsState)
- desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
- // Verify relayout occurs when status bar inset visibility changes
- verify(decoration, times(1)).relayout(task)
+ verify(decoration1, never()).onInsetsStateChanged(insetsState)
+ verify(decoration2).onInsetsStateChanged(insetsState)
+ verify(decoration3).onInsetsStateChanged(insetsState)
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
- fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() {
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
- val decoration = setUpMockDecorationForTask(task)
+ fun testKeyguardState_notifiesAllDecors() {
+ val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration1 = setUpMockDecorationForTask(task1)
+ onTaskOpening(task1)
+ val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration2 = setUpMockDecorationForTask(task2)
+ onTaskOpening(task2)
+ val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration3 = setUpMockDecorationForTask(task3)
+ onTaskOpening(task3)
- onTaskOpening(task)
+ desktopModeOnKeyguardChangedListener
+ .onKeyguardVisibilityChanged(true /* visible */, true /* occluded */,
+ false /* animatingDismiss */)
- // Add navigation bar insets source
- val insetsState = InsetsState()
- val navigationBarInsetsSourceId = 1
- val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars())
- navigationBarInsetsSource.isVisible = false
- insetsState.addSource(navigationBarInsetsSource)
-
- desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
- // Verify relayout does not occur when non-status bar inset changes visibility
- verify(decoration, never()).relayout(task)
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
- fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() {
- val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
- val decoration = setUpMockDecorationForTask(task)
-
- onTaskOpening(task)
-
- // Add status bar insets source
- val insetsState = InsetsState()
- val statusBarInsetsSourceId = 0
- val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars())
- statusBarInsetsSource.isVisible = false
- insetsState.addSource(statusBarInsetsSource)
-
- desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
- desktopModeOnInsetsChangedListener.insetsChanged(insetsState)
-
- // Verify relayout runs only once when status bar inset visibility changes.
- verify(decoration, times(1)).relayout(task)
+ verify(decoration1).onKeyguardStateChanged(true /* visible */, true /* occluded */)
+ verify(decoration2).onKeyguardStateChanged(true /* visible */, true /* occluded */)
+ verify(decoration3).onKeyguardStateChanged(true /* visible */, true /* occluded */)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index 77337a0..d8f395d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -56,7 +56,7 @@
public class DragResizeWindowGeometryTests extends ShellTestCase {
private static final Size TASK_SIZE = new Size(500, 1000);
private static final int TASK_CORNER_RADIUS = 10;
- private static final int EDGE_RESIZE_THICKNESS = 15;
+ private static final int EDGE_RESIZE_THICKNESS = 12;
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2ec3ab5..6154391c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -31,6 +31,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
@@ -63,6 +64,7 @@
import android.util.DisplayMetrics;
import android.view.AttachedSurfaceControl;
import android.view.Display;
+import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -158,6 +160,8 @@
mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
mRelayoutParams.mCornerRadius = CORNER_RADIUS;
+ when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY))
+ .thenReturn(mock(Display.class));
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
when(mMockSurfaceControlViewHost.getRootSurfaceControl())
@@ -629,15 +633,15 @@
.setVisible(true)
.setBounds(new Rect(0, 0, 1000, 1000))
.build();
+ taskInfo.isFocused = true;
+ // Caption visible at first.
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), true /* visible */));
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
-
- // Run it once so that insets are added.
- mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true);
windowDecor.relayout(taskInfo);
- // Run it again so that insets are removed.
- mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
- windowDecor.relayout(taskInfo);
+ // Hide caption so insets are removed.
+ windowDecor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(),
eq(0) /* index */, eq(captionBar()));
@@ -656,10 +660,10 @@
.setVisible(true)
.setBounds(new Rect(0, 0, 1000, 1000))
.build();
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
-
// Hidden from the beginning, so no insets were ever added.
- mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false);
+ when(mMockDisplayController.getInsetsState(taskInfo.displayId))
+ .thenReturn(createInsetsState(statusBars(), false /* visible */));
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
// Never added.
@@ -896,6 +900,78 @@
windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
}
+ @Test
+ public void onStatusBarVisibilityChange_shownToHidden_hidesCaption() {
+ final ActivityManager.RunningTaskInfo task = createTaskInfo();
+ when(mMockDisplayController.getInsetsState(task.displayId))
+ .thenReturn(createInsetsState(statusBars(), true /* visible */));
+ final TestWindowDecoration decor = createWindowDecoration(task);
+ decor.relayout(task);
+ assertTrue(decor.mIsCaptionVisible);
+
+ decor.onInsetsStateChanged(createInsetsState(statusBars(), false /* visible */));
+
+ assertFalse(decor.mIsCaptionVisible);
+ }
+
+ @Test
+ public void onStatusBarVisibilityChange_hiddenToShown_showsCaption() {
+ final ActivityManager.RunningTaskInfo task = createTaskInfo();
+ when(mMockDisplayController.getInsetsState(task.displayId))
+ .thenReturn(createInsetsState(statusBars(), false /* visible */));
+ final TestWindowDecoration decor = createWindowDecoration(task);
+ decor.relayout(task);
+ assertFalse(decor.mIsCaptionVisible);
+
+ decor.onInsetsStateChanged(createInsetsState(statusBars(), true /* visible */));
+
+ assertTrue(decor.mIsCaptionVisible);
+ }
+
+ @Test
+ public void onKeyguardStateChange_hiddenToShownAndOccluding_hidesCaption() {
+ final ActivityManager.RunningTaskInfo task = createTaskInfo();
+ when(mMockDisplayController.getInsetsState(task.displayId))
+ .thenReturn(createInsetsState(statusBars(), true /* visible */));
+ final TestWindowDecoration decor = createWindowDecoration(task);
+ decor.relayout(task);
+ assertTrue(decor.mIsCaptionVisible);
+
+ decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
+
+ assertFalse(decor.mIsCaptionVisible);
+ }
+
+ @Test
+ public void onKeyguardStateChange_showingAndOccludingToHidden_showsCaption() {
+ final ActivityManager.RunningTaskInfo task = createTaskInfo();
+ when(mMockDisplayController.getInsetsState(task.displayId))
+ .thenReturn(createInsetsState(statusBars(), true /* visible */));
+ final TestWindowDecoration decor = createWindowDecoration(task);
+ decor.onKeyguardStateChanged(true /* visible */, true /* occluding */);
+ assertFalse(decor.mIsCaptionVisible);
+
+ decor.onKeyguardStateChanged(false /* visible */, false /* occluding */);
+
+ assertTrue(decor.mIsCaptionVisible);
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo() {
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ return taskInfo;
+ }
+
+ private InsetsState createInsetsState(@WindowInsets.Type.InsetsType int type, boolean visible) {
+ final InsetsState state = new InsetsState();
+ final InsetsSource source = new InsetsSource(0, type);
+ source.setVisible(visible);
+ state.addSource(source);
+ return state;
+ }
+
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mContext, mMockDisplayController,
mMockShellTaskOrganizer, taskInfo, mMockTaskSurface,
@@ -961,10 +1037,24 @@
return null;
}
+ @Override
+ int getCaptionViewId() {
+ return R.id.caption;
+ }
+
+ @Override
+ TestView inflateLayout(Context context, int layoutResId) {
+ if (layoutResId == R.layout.caption_layout) {
+ return mMockView;
+ }
+ return super.inflateLayout(context, layoutResId);
+ }
+
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw) {
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ mRelayoutParams.mLayoutResId = R.layout.caption_layout;
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 9824190..f066e46 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -121,7 +121,7 @@
uint8_t target_assigned_package_id)
: data_header_(data_header),
entries_(entries),
- target_assigned_package_id_(target_assigned_package_id) { };
+ target_assigned_package_id_(target_assigned_package_id) {}
status_t OverlayDynamicRefTable::lookupResourceId(uint32_t* resId) const {
const Idmap_overlay_entry* first_entry = entries_;
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index c32a38e..64b1f0c 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -171,14 +171,14 @@
}
// Returns a mapping from target resource ids to overlay values.
- const IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
- const OverlayDynamicRefTable* overlay_ref_table) const {
+ IdmapResMap GetTargetResourcesMap(uint8_t target_assigned_package_id,
+ const OverlayDynamicRefTable* overlay_ref_table) const {
return IdmapResMap(data_header_, target_entries_, target_inline_entries_, inline_entry_values_,
configurations_, target_assigned_package_id, overlay_ref_table);
}
// Returns a dynamic reference table for a loaded overlay package.
- const OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
+ OverlayDynamicRefTable GetOverlayDynamicRefTable(uint8_t target_assigned_package_id) const {
return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d184f64..1217b47 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -42,6 +42,9 @@
constexpr bool initialize_gl_always() {
return false;
}
+constexpr bool resample_gainmap_regions() {
+ return false;
+}
} // namespace hwui_flags
#endif
@@ -100,6 +103,7 @@
bool Properties::clipSurfaceViews = false;
bool Properties::hdr10bitPlus = false;
+bool Properties::resampleGainmapRegions = false;
int Properties::timeoutMultiplier = 1;
@@ -175,6 +179,8 @@
clipSurfaceViews =
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
hdr10bitPlus = hwui_flags::hdr_10bit_plus();
+ resampleGainmapRegions = base::GetBoolProperty("debug.hwui.resample_gainmap_regions",
+ hwui_flags::resample_gainmap_regions());
timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index e264642..73e80ce 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -342,6 +342,7 @@
static bool clipSurfaceViews;
static bool hdr10bitPlus;
+ static bool resampleGainmapRegions;
static int timeoutMultiplier;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index cd3ae53..13c0b00 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -97,3 +97,13 @@
description: "Initialize GL even when HWUI is set to use Vulkan. This improves app startup time for apps using GL."
bug: "335172671"
}
+
+flag {
+ name: "resample_gainmap_regions"
+ namespace: "core_graphics"
+ description: "Resample gainmaps when decoding regions, to improve visual quality"
+ bug: "352847821"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index ea5c144..6a65b82 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -87,8 +87,17 @@
requireUnpremul, prefColorSpace);
}
- bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, int outWidth, int outHeight,
- const SkIRect& desiredSubset, int sampleSize, bool requireUnpremul) {
+ // Decodes the gainmap region. If decoding succeeded, returns true and
+ // populate outGainmap with the decoded gainmap. Otherwise, returns false.
+ //
+ // Note that the desiredSubset is the logical region within the source
+ // gainmap that we want to decode. This is used for scaling into the final
+ // bitmap, since we do not want to include portions of the gainmap outside
+ // of this region. desiredSubset is also _not_ guaranteed to be
+ // pixel-aligned, so it's not possible to simply resize the resulting
+ // bitmap to accomplish this.
+ bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, SkISize bitmapDimensions,
+ const SkRect& desiredSubset, int sampleSize, bool requireUnpremul) {
SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
sk_sp<SkColorSpace> decodeColorSpace =
mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
@@ -107,13 +116,30 @@
// allocation type. RecyclingClippingPixelAllocator will populate this with the
// actual alpha type in either allocPixelRef() or copyIfNecessary()
sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
- outWidth, outHeight, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
+ bitmapDimensions, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap for Gainmap");
return false;
}
- RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false);
- if (!mGainmapBRD->decodeRegion(&bm, &allocator, desiredSubset, sampleSize, decodeColorType,
+
+ // Round out the subset so that we decode a slightly larger region, in
+ // case the subset has fractional components.
+ SkIRect roundedSubset = desiredSubset.roundOut();
+
+ // Map the desired subset to the space of the decoded gainmap. The
+ // subset is repositioned relative to the resulting bitmap, and then
+ // scaled to respect the sampleSize.
+ // This assumes that the subset will not be modified by the decoder, which is true
+ // for existing gainmap formats.
+ SkRect logicalSubset = desiredSubset.makeOffset(-std::floorf(desiredSubset.left()),
+ -std::floorf(desiredSubset.top()));
+ logicalSubset.fLeft /= sampleSize;
+ logicalSubset.fTop /= sampleSize;
+ logicalSubset.fRight /= sampleSize;
+ logicalSubset.fBottom /= sampleSize;
+
+ RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false, logicalSubset);
+ if (!mGainmapBRD->decodeRegion(&bm, &allocator, roundedSubset, sampleSize, decodeColorType,
requireUnpremul, decodeColorSpace)) {
ALOGE("Error decoding Gainmap region");
return false;
@@ -130,16 +156,31 @@
return true;
}
- SkIRect calculateGainmapRegion(const SkIRect& mainImageRegion, int* inOutWidth,
- int* inOutHeight) {
+ struct Projection {
+ SkRect srcRect;
+ SkISize destSize;
+ };
+ Projection calculateGainmapRegion(const SkIRect& mainImageRegion, SkISize dimensions) {
const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
- *inOutWidth *= scaleX;
- *inOutHeight *= scaleY;
- // TODO: Account for rounding error?
- return SkIRect::MakeLTRB(mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
- mainImageRegion.right() * scaleX,
- mainImageRegion.bottom() * scaleY);
+
+ if (uirenderer::Properties::resampleGainmapRegions) {
+ const auto srcRect = SkRect::MakeLTRB(
+ mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
+ mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY);
+ // Request a slightly larger destination size so that the gainmap
+ // subset we want fits entirely in this size.
+ const auto destSize = SkISize::Make(std::ceil(dimensions.width() * scaleX),
+ std::ceil(dimensions.height() * scaleY));
+ return Projection{.srcRect = srcRect, .destSize = destSize};
+ } else {
+ const auto srcRect = SkRect::Make(SkIRect::MakeLTRB(
+ mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
+ mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY));
+ const auto destSize =
+ SkISize::Make(dimensions.width() * scaleX, dimensions.height() * scaleY);
+ return Projection{.srcRect = srcRect, .destSize = destSize};
+ }
}
bool hasGainmap() { return mGainmapBRD != nullptr; }
@@ -327,16 +368,16 @@
sp<uirenderer::Gainmap> gainmap;
bool hasGainmap = brd->hasGainmap();
if (hasGainmap) {
- int gainmapWidth = bitmap.width();
- int gainmapHeight = bitmap.height();
+ SkISize gainmapDims = SkISize::Make(bitmap.width(), bitmap.height());
if (javaBitmap) {
// If we are recycling we must match the inBitmap's relative dimensions
- gainmapWidth = recycledBitmap->width();
- gainmapHeight = recycledBitmap->height();
+ gainmapDims.fWidth = recycledBitmap->width();
+ gainmapDims.fHeight = recycledBitmap->height();
}
- SkIRect gainmapSubset = brd->calculateGainmapRegion(subset, &gainmapWidth, &gainmapHeight);
- if (!brd->decodeGainmapRegion(&gainmap, gainmapWidth, gainmapHeight, gainmapSubset,
- sampleSize, requireUnpremul)) {
+ BitmapRegionDecoderWrapper::Projection gainmapProjection =
+ brd->calculateGainmapRegion(subset, gainmapDims);
+ if (!brd->decodeGainmapRegion(&gainmap, gainmapProjection.destSize,
+ gainmapProjection.srcRect, sampleSize, requireUnpremul)) {
// If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
hasGainmap = false;
}
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index a88139d..258bf91 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,12 +1,14 @@
#include <assert.h>
+#include <cutils/ashmem.h>
+#include <hwui/Canvas.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
#include <unistd.h>
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
#include "GraphicsJNI.h"
-
#include "SkBitmap.h"
#include "SkCanvas.h"
+#include "SkColor.h"
#include "SkColorSpace.h"
#include "SkFontMetrics.h"
#include "SkImageInfo.h"
@@ -14,10 +16,9 @@
#include "SkPoint.h"
#include "SkRect.h"
#include "SkRegion.h"
+#include "SkSamplingOptions.h"
#include "SkTypes.h"
-#include <cutils/ashmem.h>
-#include <hwui/Canvas.h>
-#include <log/log.h>
+#include "jni.h"
using namespace android;
@@ -630,13 +631,15 @@
////////////////////////////////////////////////////////////////////////////////
-RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
- bool mustMatchColorType)
+RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(
+ android::Bitmap* recycledBitmap, bool mustMatchColorType,
+ std::optional<SkRect> desiredSubset)
: mRecycledBitmap(recycledBitmap)
, mRecycledBytes(recycledBitmap ? recycledBitmap->getAllocationByteCount() : 0)
, mSkiaBitmap(nullptr)
, mNeedsCopy(false)
- , mMustMatchColorType(mustMatchColorType) {}
+ , mMustMatchColorType(mustMatchColorType)
+ , mDesiredSubset(getSourceBoundsForUpsample(desiredSubset)) {}
RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {}
@@ -668,7 +671,8 @@
const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight);
const size_t rowBytes = maxInfo.minRowBytes();
const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes);
- if (bytesNeeded <= mRecycledBytes) {
+
+ if (!mDesiredSubset && bytesNeeded <= mRecycledBytes) {
// Here we take advantage of reconfigure() to reset the rowBytes
// of mRecycledBitmap. It is very important that we pass in
// mRecycledBitmap->info() for the SkImageInfo. According to the
@@ -712,20 +716,31 @@
if (mNeedsCopy) {
mRecycledBitmap->ref();
android::Bitmap* recycledPixels = mRecycledBitmap;
- void* dst = recycledPixels->pixels();
- const size_t dstRowBytes = mRecycledBitmap->rowBytes();
- const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(),
- mSkiaBitmap->info().minRowBytes());
- const int rowsToCopy = std::min(mRecycledBitmap->info().height(),
- mSkiaBitmap->info().height());
- for (int y = 0; y < rowsToCopy; y++) {
- memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
- // Cast to bytes in order to apply the dstRowBytes offset correctly.
- dst = reinterpret_cast<void*>(
- reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
+ if (mDesiredSubset) {
+ recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
+ recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
+
+ auto canvas = SkCanvas(recycledPixels->getSkBitmap());
+ SkRect destination = SkRect::Make(recycledPixels->info().bounds());
+ destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds()));
+ canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination,
+ SkSamplingOptions(SkFilterMode::kLinear), nullptr,
+ SkCanvas::kFast_SrcRectConstraint);
+ } else {
+ void* dst = recycledPixels->pixels();
+ const size_t dstRowBytes = mRecycledBitmap->rowBytes();
+ const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(),
+ mSkiaBitmap->info().minRowBytes());
+ const int rowsToCopy =
+ std::min(mRecycledBitmap->info().height(), mSkiaBitmap->info().height());
+ for (int y = 0; y < rowsToCopy; y++) {
+ memcpy(dst, mSkiaBitmap->getAddr(0, y), bytesToCopy);
+ // Cast to bytes in order to apply the dstRowBytes offset correctly.
+ dst = reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
+ }
+ recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
+ recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
}
- recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
- recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
recycledPixels->notifyPixelsChanged();
recycledPixels->unref();
}
@@ -733,6 +748,20 @@
mSkiaBitmap = nullptr;
}
+std::optional<SkRect> RecyclingClippingPixelAllocator::getSourceBoundsForUpsample(
+ std::optional<SkRect> subset) {
+ if (!uirenderer::Properties::resampleGainmapRegions || !subset || subset->isEmpty()) {
+ return std::nullopt;
+ }
+
+ if (subset->left() == floor(subset->left()) && subset->top() == floor(subset->top()) &&
+ subset->right() == floor(subset->right()) && subset->bottom() == floor(subset->bottom())) {
+ return std::nullopt;
+ }
+
+ return subset;
+}
+
////////////////////////////////////////////////////////////////////////////////
AshmemPixelAllocator::AshmemPixelAllocator(JNIEnv *env) {
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index b0a1074..4b08f8d 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -216,8 +216,8 @@
*/
class RecyclingClippingPixelAllocator : public android::skia::BRDAllocator {
public:
- RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
- bool mustMatchColorType = true);
+ RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap, bool mustMatchColorType = true,
+ std::optional<SkRect> desiredSubset = std::nullopt);
~RecyclingClippingPixelAllocator();
@@ -241,11 +241,24 @@
SkCodec::ZeroInitialized zeroInit() const override { return SkCodec::kNo_ZeroInitialized; }
private:
+ /**
+ * Optionally returns a subset rectangle that we need to upsample from.
+ * E.g., a gainmap subset may be decoded in a slightly larger rectangle
+ * than is needed (in order to correctly preserve gainmap alignment when
+ * rendering at display time), so we need to re-sample the "intended"
+ * gainmap back up to the bitmap dimensions.
+ *
+ * If we don't need to upsample from a subregion, then returns an empty
+ * optional
+ */
+ static std::optional<SkRect> getSourceBoundsForUpsample(std::optional<SkRect> subset);
+
android::Bitmap* mRecycledBitmap;
const size_t mRecycledBytes;
SkBitmap* mSkiaBitmap;
bool mNeedsCopy;
const bool mMustMatchColorType;
+ const std::optional<SkRect> mDesiredSubset;
};
class AshmemPixelAllocator : public SkBitmap::Allocator {
diff --git a/media/jni/JetPlayer.h b/media/jni/JetPlayer.h
index bb569bc..4cc266d 100644
--- a/media/jni/JetPlayer.h
+++ b/media/jni/JetPlayer.h
@@ -40,7 +40,7 @@
static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
static const int JET_PAUSE_UPDATE = 4;
- JetPlayer(void *javaJetPlayer,
+ explicit JetPlayer(void *javaJetPlayer,
int maxTracks = 32,
int trackBufferSize = 1200);
~JetPlayer();
@@ -69,7 +69,6 @@
void fireUpdateOnStatusChange();
void fireEventsFromJetQueue();
- JetPlayer() {} // no default constructor
void dump();
void dumpJetStatus(S_JET_STATUS* pJetStatus);
@@ -96,7 +95,7 @@
class JetPlayerThread : public Thread {
public:
- JetPlayerThread(JetPlayer *player) : mPlayer(player) {
+ explicit JetPlayerThread(JetPlayer *player) : mPlayer(player) {
}
protected:
@@ -106,8 +105,7 @@
JetPlayer *mPlayer;
bool threadLoop() {
- int result;
- result = mPlayer->render();
+ mPlayer->render();
return false;
}
diff --git a/native/android/surface_control_input_receiver.cpp b/native/android/surface_control_input_receiver.cpp
index a84ec73..7caa943 100644
--- a/native/android/surface_control_input_receiver.cpp
+++ b/native/android/surface_control_input_receiver.cpp
@@ -41,7 +41,7 @@
const sp<IBinder>& clientToken, const sp<InputTransferToken>& inputTransferToken,
AInputReceiverCallbacks* callbacks)
: mCallbacks(callbacks),
- mInputConsumer(inputChannel, looper, *this),
+ mInputConsumer(inputChannel, looper, *this, nullptr),
mClientToken(clientToken),
mInputTransferToken(inputTransferToken) {}
diff --git a/nfc/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
index 0e40db6..0eb846d 100644
--- a/nfc/java/android/nfc/NfcActivityManager.java
+++ b/nfc/java/android/nfc/NfcActivityManager.java
@@ -236,11 +236,7 @@
public void setReaderMode(Binder token, int flags, Bundle extras) {
if (DBG) Log.d(TAG, "Setting reader mode");
- try {
- NfcAdapter.sService.setReaderMode(token, this, flags, extras);
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode(token, this, flags, extras));
}
/**
@@ -248,19 +244,11 @@
* Makes IPC call - do not hold lock.
*/
void requestNfcServiceCallback() {
- try {
- NfcAdapter.sService.setAppCallback(this);
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(() -> NfcAdapter.sService.setAppCallback(this));
}
void verifyNfcPermission() {
- try {
- NfcAdapter.sService.verifyNfcPermission();
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(() -> NfcAdapter.sService.verifyNfcPermission());
}
@Override
@@ -406,11 +394,8 @@
}
private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
- try {
- NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(
+ () -> NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech));
}
}
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 0ffab4b..b36b705 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -922,8 +922,8 @@
* @hide
*/
@UnsupportedAppUsage
- public INfcAdapter getService() {
- isEnabled(); // NOP call to recover sService if it is stale
+ public static INfcAdapter getService() {
+ isEnabledStatic(); // NOP call to recover sService if it is stale
return sService;
}
@@ -931,8 +931,8 @@
* Returns the binder interface to the tag service.
* @hide
*/
- public INfcTag getTagService() {
- isEnabled(); // NOP call to recover sTagService if it is stale
+ public static INfcTag getTagService() {
+ isEnabledStatic(); // NOP call to recover sTagService if it is stale
return sTagService;
}
@@ -940,8 +940,8 @@
* Returns the binder interface to the card emulation service.
* @hide
*/
- public INfcCardEmulation getCardEmulationService() {
- isEnabled();
+ public static INfcCardEmulation getCardEmulationService() {
+ isEnabledStatic();
return sCardEmulationService;
}
@@ -949,8 +949,8 @@
* Returns the binder interface to the NFC-F card emulation service.
* @hide
*/
- public INfcFCardEmulation getNfcFCardEmulationService() {
- isEnabled();
+ public static INfcFCardEmulation getNfcFCardEmulationService() {
+ isEnabledStatic();
return sNfcFCardEmulationService;
}
@@ -973,14 +973,14 @@
* @hide
*/
@UnsupportedAppUsage
- public void attemptDeadServiceRecovery(Exception e) {
+ public static void attemptDeadServiceRecovery(RemoteException e) {
Log.e(TAG, "NFC service dead - attempting to recover", e);
INfcAdapter service = getServiceInterface();
if (service == null) {
Log.e(TAG, "could not retrieve NFC service during service recovery");
// nothing more can be done now, sService is still stale, we'll hit
// this recovery path again later
- return;
+ e.rethrowAsRuntimeException();
}
// assigning to sService is not thread-safe, but this is best-effort code
// and on a well-behaved system should never happen
@@ -993,7 +993,7 @@
Log.e(TAG, "could not retrieve NFC tag service during service recovery");
// nothing more can be done now, sService is still stale, we'll hit
// this recovery path again later
- return;
+ ee.rethrowAsRuntimeException();
}
}
@@ -1014,24 +1014,27 @@
"could not retrieve NFC-F card emulation service during service recovery");
}
}
-
- return;
}
- private boolean isCardEmulationEnabled() {
+ private static boolean isCardEmulationEnabled() {
if (sHasCeFeature) {
return (sCardEmulationService != null || sNfcFCardEmulationService != null);
}
return false;
}
- private boolean isTagReadingEnabled() {
+ private static boolean isTagReadingEnabled() {
if (sHasNfcFeature) {
return sTagService != null;
}
return false;
}
+ private static boolean isEnabledStatic() {
+ boolean serviceState = callServiceReturn(() -> sService.getState() == STATE_ON, false);
+ return serviceState
+ && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
+ }
/**
* Return true if this NFC Adapter has any features enabled.
@@ -1046,24 +1049,7 @@
* @return true if this NFC Adapter has any features enabled
*/
public boolean isEnabled() {
- boolean serviceState = false;
- try {
- serviceState = sService.getState() == STATE_ON;
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return false;
- }
- try {
- serviceState = sService.getState() == STATE_ON;
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- }
- return serviceState
- && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
+ return isEnabledStatic();
}
/**
@@ -1157,11 +1143,7 @@
* @hide
*/
public void pausePolling(int timeoutInMs) {
- try {
- sService.pausePolling(timeoutInMs);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ callService(() -> sService.pausePolling(timeoutInMs));
}
@@ -1222,11 +1204,7 @@
* @hide
*/
public void resumePolling() {
- try {
- sService.resumePolling();
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ callService(() -> sService.resumePolling());
}
/**
@@ -1645,15 +1623,10 @@
if (activity == null || intent == null) {
throw new NullPointerException();
}
- try {
- TechListParcel parcel = null;
- if (techLists != null && techLists.length > 0) {
- parcel = new TechListParcel(techLists);
- }
- sService.setForegroundDispatch(intent, filters, parcel);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ final TechListParcel parcel = (techLists != null && techLists.length > 0)
+ ? new TechListParcel(techLists)
+ : null;
+ callService(() -> sService.setForegroundDispatch(intent, filters, parcel));
}
/**
@@ -1677,11 +1650,7 @@
throw new UnsupportedOperationException();
}
}
- try {
- sService.setForegroundDispatch(null, null, null);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ callService(() -> sService.setForegroundDispatch(null, null, null));
}
/**
@@ -1762,11 +1731,7 @@
}
Binder token = new Binder();
int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
- try {
- NfcAdapter.sService.setReaderMode(token, null, flags, null);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ callService(() -> sService.setReaderMode(token, null, flags, null));
}
/**
@@ -1838,12 +1803,8 @@
&& ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH
|| (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {
Binder token = new Binder();
- try {
- NfcAdapter.sService.updateDiscoveryTechnology(token,
- pollTechnology, listenTechnology);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ callService( () ->
+ sService.updateDiscoveryTechnology(token, pollTechnology, listenTechnology));
} else {
mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
}
@@ -2227,11 +2188,7 @@
if (tag == null) {
throw new NullPointerException("tag cannot be null");
}
- try {
- sService.dispatch(tag);
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- }
+ callService(() -> sService.dispatch(tag));
}
/**
@@ -2267,8 +2224,10 @@
synchronized (mLock) {
if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
// update the tag technologies
- sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
- mNfcUnlockHandlers.remove(unlockHandler);
+ callService(() -> {
+ sService.removeNfcUnlockHandler(mNfcUnlockHandlers.get(unlockHandler));
+ mNfcUnlockHandlers.remove(unlockHandler);
+ });
}
INfcUnlockHandler.Stub iHandler = new INfcUnlockHandler.Stub() {
@@ -2277,20 +2236,18 @@
return unlockHandler.onUnlockAttempted(tag);
}
};
-
- sService.addNfcUnlockHandler(iHandler,
- Tag.getTechCodesFromStrings(tagTechnologies));
- mNfcUnlockHandlers.put(unlockHandler, iHandler);
+ return callServiceReturn(() -> {
+ sService.addNfcUnlockHandler(
+ iHandler, Tag.getTechCodesFromStrings(tagTechnologies));
+ mNfcUnlockHandlers.put(unlockHandler, iHandler);
+ return true;
+ }, false);
}
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to register LockscreenDispatch", e);
return false;
}
- return true;
}
/**
@@ -2307,17 +2264,14 @@
throw new UnsupportedOperationException();
}
}
- try {
- synchronized (mLock) {
- if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+ synchronized (mLock) {
+ if (mNfcUnlockHandlers.containsKey(unlockHandler)) {
+ return callServiceReturn(() -> {
sService.removeNfcUnlockHandler(mNfcUnlockHandlers.remove(unlockHandler));
- }
-
- return true;
+ return true;
+ }, false);
}
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- return false;
+ return true;
}
}
@@ -2525,26 +2479,8 @@
Log.e(TAG, "TagIntentAppPreference is not supported");
throw new UnsupportedOperationException();
}
- try {
- Map<String, Boolean> result = (Map<String, Boolean>) sService
- .getTagIntentAppPreferenceForUser(userId);
- return result;
- } catch (RemoteException e) {
- attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return Collections.emptyMap();
- }
- try {
- Map<String, Boolean> result = (Map<String, Boolean>) sService
- .getTagIntentAppPreferenceForUser(userId);
- return result;
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
- }
- return Collections.emptyMap();
- }
+ return callServiceReturn( () ->
+ sService.getTagIntentAppPreferenceForUser(userId), Collections.emptyMap());
}
/**
@@ -2590,50 +2526,44 @@
callService(() -> sService.notifyTestHceData(technology, data));
}
+ /** @hide */
interface ServiceCall {
void call() throws RemoteException;
}
-
- void callService(ServiceCall call) {
+ /** @hide */
+ static void callService(ServiceCall call) {
try {
if (sService == null) {
- attemptDeadServiceRecovery(null);
+ attemptDeadServiceRecovery(new RemoteException("NFC Service is null"));
}
call.call();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
- // Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return;
- }
try {
call.call();
} catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
+ ee.rethrowAsRuntimeException();
}
}
}
+ /** @hide */
interface ServiceCallReturn<T> {
T call() throws RemoteException;
}
- <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+ /** @hide */
+ static <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
try {
if (sService == null) {
- attemptDeadServiceRecovery(null);
+ attemptDeadServiceRecovery(new RemoteException("NFC Service is null"));
}
return call.call();
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
- if (sService == null) {
- Log.e(TAG, "Failed to recover NFC Service.");
- return defaultReturn;
- }
try {
return call.call();
} catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover NFC Service.");
+ ee.rethrowAsRuntimeException();
}
}
return defaultReturn;
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index f6138a6..2ec819c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -89,13 +89,11 @@
+ "registering");
throw new IllegalArgumentException();
}
- try {
+ NfcAdapter.callService(() -> {
NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback);
mCallback = callback;
mExecutor = executor;
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ });
}
}
@@ -117,13 +115,11 @@
Log.e(TAG, "Callback not registered");
throw new IllegalArgumentException();
}
- try {
+ NfcAdapter.callService(() -> {
NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback);
mCallback = null;
mExecutor = null;
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ });
}
}
@@ -134,11 +130,7 @@
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void clearPreference() {
- try {
- NfcAdapter.sService.clearPreference();
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(() -> NfcAdapter.sService.clearPreference());
}
/**
@@ -147,11 +139,7 @@
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void synchronizeScreenState() {
- try {
- NfcAdapter.sService.setScreenState();
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(() -> NfcAdapter.sService.setScreenState());
}
/**
@@ -162,11 +150,7 @@
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void maybeTriggerFirmwareUpdate() {
- try {
- NfcAdapter.sService.checkFirmware();
- } catch (RemoteException e) {
- mAdapter.attemptDeadServiceRecovery(e);
- }
+ NfcAdapter.callService(() -> NfcAdapter.sService.checkFirmware());
}
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 2fe2ce3..e0438ce 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -219,24 +219,9 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*/
public boolean isDefaultServiceForCategory(ComponentName service, String category) {
- try {
- return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
- service, category);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(),
- service, category);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.isDefaultServiceForCategory(
+ mContext.getUser().getIdentifier(), service, category), false);
}
/**
@@ -251,24 +236,9 @@
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*/
public boolean isDefaultServiceForAid(ComponentName service, String aid) {
- try {
- return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
- service, aid);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(),
- service, aid);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.isDefaultServiceForAid(
+ mContext.getUser().getIdentifier(), service, aid), false);
}
/**
@@ -331,22 +301,8 @@
*/
public int getSelectionModeForCategory(String category) {
if (CATEGORY_PAYMENT.equals(category)) {
- boolean paymentRegistered = false;
- try {
- paymentRegistered = sService.isDefaultPaymentRegistered();
- } catch (RemoteException e) {
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return SELECTION_MODE_ALWAYS_ASK;
- }
- try {
- paymentRegistered = sService.isDefaultPaymentRegistered();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return SELECTION_MODE_ALWAYS_ASK;
- }
- }
+ boolean paymentRegistered = callServiceReturn(() ->
+ sService.isDefaultPaymentRegistered(), false);
if (paymentRegistered) {
return SELECTION_MODE_PREFER_DEFAULT;
} else {
@@ -369,13 +325,9 @@
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
public boolean setShouldDefaultToObserveModeForService(@NonNull ComponentName service,
boolean enable) {
- try {
- return sService.setShouldDefaultToObserveModeForService(
- mContext.getUser().getIdentifier(), service, enable);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- }
- return false;
+ return callServiceReturn(() ->
+ sService.setShouldDefaultToObserveModeForService(
+ mContext.getUser().getIdentifier(), service, enable), false);
}
/**
@@ -396,27 +348,11 @@
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
@NonNull String pollingLoopFilter, boolean autoTransact) {
- pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter);
-
- try {
- return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
- service, pollingLoopFilter, autoTransact);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.registerPollingLoopFilterForService(
- mContext.getUser().getIdentifier(), service,
- pollingLoopFilter, autoTransact);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ final String pollingLoopFilterV = validatePollingLoopFilter(pollingLoopFilter);
+ return callServiceReturn(() ->
+ sService.registerPollingLoopFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopFilterV, autoTransact),
+ false);
}
/**
@@ -431,27 +367,10 @@
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public boolean removePollingLoopFilterForService(@NonNull ComponentName service,
@NonNull String pollingLoopFilter) {
- pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter);
-
- try {
- return sService.removePollingLoopFilterForService(mContext.getUser().getIdentifier(),
- service, pollingLoopFilter);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.removePollingLoopFilterForService(
- mContext.getUser().getIdentifier(), service,
- pollingLoopFilter);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ final String pollingLoopFilterV = validatePollingLoopFilter(pollingLoopFilter);
+ return callServiceReturn(() ->
+ sService.removePollingLoopFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopFilterV), false);
}
@@ -477,28 +396,13 @@
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public boolean registerPollingLoopPatternFilterForService(@NonNull ComponentName service,
@NonNull String pollingLoopPatternFilter, boolean autoTransact) {
- pollingLoopPatternFilter = validatePollingLoopPatternFilter(pollingLoopPatternFilter);
-
- try {
- return sService.registerPollingLoopPatternFilterForService(
- mContext.getUser().getIdentifier(),
- service, pollingLoopPatternFilter, autoTransact);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.registerPollingLoopPatternFilterForService(
- mContext.getUser().getIdentifier(), service,
- pollingLoopPatternFilter, autoTransact);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ final String pollingLoopPatternFilterV =
+ validatePollingLoopPatternFilter(pollingLoopPatternFilter);
+ return callServiceReturn(() ->
+ sService.registerPollingLoopPatternFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopPatternFilterV,
+ autoTransact),
+ false);
}
/**
@@ -518,27 +422,11 @@
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public boolean removePollingLoopPatternFilterForService(@NonNull ComponentName service,
@NonNull String pollingLoopPatternFilter) {
- pollingLoopPatternFilter = validatePollingLoopPatternFilter(pollingLoopPatternFilter);
-
- try {
- return sService.removePollingLoopPatternFilterForService(
- mContext.getUser().getIdentifier(), service, pollingLoopPatternFilter);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.removePollingLoopPatternFilterForService(
- mContext.getUser().getIdentifier(), service,
- pollingLoopPatternFilter);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ final String pollingLoopPatternFilterV =
+ validatePollingLoopPatternFilter(pollingLoopPatternFilter);
+ return callServiceReturn(() ->
+ sService.removePollingLoopPatternFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopPatternFilterV), false);
}
/**
@@ -563,25 +451,10 @@
*/
public boolean registerAidsForService(ComponentName service, String category,
List<String> aids) {
- AidGroup aidGroup = new AidGroup(aids, category);
- try {
- return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
- service, aidGroup);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.registerAidGroupForService(mContext.getUser().getIdentifier(),
- service, aidGroup);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ final AidGroup aidGroup = new AidGroup(aids, category);
+ return callServiceReturn(() ->
+ sService.registerAidGroupForService(
+ mContext.getUser().getIdentifier(), service, aidGroup), false);
}
/**
@@ -603,27 +476,9 @@
@RequiresPermission(android.Manifest.permission.NFC)
@NonNull
public boolean unsetOffHostForService(@NonNull ComponentName service) {
- NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
- if (adapter == null) {
- return false;
- }
-
- try {
- return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.unsetOffHostForService(
+ mContext.getUser().getIdentifier(), service), false);
}
/**
@@ -662,8 +517,6 @@
@NonNull
public boolean setOffHostForService(@NonNull ComponentName service,
@NonNull String offHostSecureElement) {
- boolean validSecureElement = false;
-
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
if (adapter == null || offHostSecureElement == null) {
return false;
@@ -684,25 +537,10 @@
} else if (offHostSecureElement.equals("SIM")) {
offHostSecureElement = "SIM1";
}
-
- try {
- return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
- offHostSecureElement);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.setOffHostForService(mContext.getUser().getIdentifier(), service,
- offHostSecureElement);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ final String offHostSecureElementV = new String(offHostSecureElement);
+ return callServiceReturn(() ->
+ sService.setOffHostForService(
+ mContext.getUser().getIdentifier(), service, offHostSecureElementV), false);
}
/**
@@ -720,25 +558,10 @@
* @return The list of AIDs registered for this category, or null if it couldn't be found.
*/
public List<String> getAidsForService(ComponentName service, String category) {
- try {
- AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
- service, category);
- return (group != null ? group.getAids() : null);
- } catch (RemoteException e) {
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return null;
- }
- try {
- AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(),
- service, category);
- return (group != null ? group.getAids() : null);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return null;
- }
- }
+ AidGroup group = callServiceReturn(() ->
+ sService.getAidGroupForService(
+ mContext.getUser().getIdentifier(), service, category), null);
+ return (group != null ? group.getAids() : null);
}
/**
@@ -757,24 +580,9 @@
* @return whether the group was successfully removed.
*/
public boolean removeAidsForService(ComponentName service, String category) {
- try {
- return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), service,
- category);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.removeAidGroupForService(mContext.getUser().getIdentifier(),
- service, category);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.removeAidGroupForService(
+ mContext.getUser().getIdentifier(), service, category), false);
}
/**
@@ -811,22 +619,7 @@
if (activity == null || service == null) {
throw new NullPointerException("activity or service or category is null");
}
- try {
- return sService.setPreferredService(service);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.setPreferredService(service);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() -> sService.setPreferredService(service), false);
}
/**
@@ -843,22 +636,7 @@
if (activity == null) {
throw new NullPointerException("activity is null");
}
- try {
- return sService.unsetPreferredService();
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.unsetPreferredService();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() -> sService.unsetPreferredService(), false);
}
/**
@@ -872,21 +650,7 @@
* @return whether AID prefix registering is supported on this device.
*/
public boolean supportsAidPrefixRegistration() {
- try {
- return sService.supportsAidPrefixRegistration();
- } catch (RemoteException e) {
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.supportsAidPrefixRegistration();
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() -> sService.supportsAidPrefixRegistration(), false);
}
/**
@@ -897,25 +661,9 @@
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
@Nullable
public List<String> getAidsForPreferredPaymentService() {
- try {
- ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
- mContext.getUser().getIdentifier());
- return (serviceInfo != null ? serviceInfo.getAids() : null);
- } catch (RemoteException e) {
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- throw e.rethrowFromSystemServer();
- }
- try {
- ApduServiceInfo serviceInfo =
- sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
- return (serviceInfo != null ? serviceInfo.getAids() : null);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- throw e.rethrowFromSystemServer();
- }
- }
+ ApduServiceInfo serviceInfo = callServiceReturn(() ->
+ sService.getPreferredPaymentService(mContext.getUser().getIdentifier()), null);
+ return (serviceInfo != null ? serviceInfo.getAids() : null);
}
/**
@@ -944,40 +692,16 @@
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
@Nullable
public String getRouteDestinationForPreferredPaymentService() {
- try {
- ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
- mContext.getUser().getIdentifier());
- if (serviceInfo != null) {
- if (!serviceInfo.isOnHost()) {
- return serviceInfo.getOffHostSecureElement() == null ?
- "OffHost" : serviceInfo.getOffHostSecureElement();
- }
- return "Host";
+ ApduServiceInfo serviceInfo = callServiceReturn(() ->
+ sService.getPreferredPaymentService(mContext.getUser().getIdentifier()), null);
+ if (serviceInfo != null) {
+ if (!serviceInfo.isOnHost()) {
+ return serviceInfo.getOffHostSecureElement() == null ?
+ "OffHost" : serviceInfo.getOffHostSecureElement();
}
- return null;
- } catch (RemoteException e) {
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- throw e.rethrowFromSystemServer();
- }
- try {
- ApduServiceInfo serviceInfo =
- sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
- if (serviceInfo != null) {
- if (!serviceInfo.isOnHost()) {
- return serviceInfo.getOffHostSecureElement() == null ?
- "Offhost" : serviceInfo.getOffHostSecureElement();
- }
- return "Host";
- }
- return null;
-
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- throw e.rethrowFromSystemServer();
- }
+ return "Host";
}
+ return null;
}
/**
@@ -995,115 +719,44 @@
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
- try {
- ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(
- mContext.getUser().getIdentifier());
- return (serviceInfo != null ? serviceInfo.getDescription() : null);
- } catch (RemoteException e) {
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- throw e.rethrowFromSystemServer();
- }
- try {
- ApduServiceInfo serviceInfo =
- sService.getPreferredPaymentService(mContext.getUser().getIdentifier());
- return (serviceInfo != null ? serviceInfo.getDescription() : null);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- throw e.rethrowFromSystemServer();
- }
- }
+ ApduServiceInfo serviceInfo = callServiceReturn(() ->
+ sService.getPreferredPaymentService(mContext.getUser().getIdentifier()), null);
+ return (serviceInfo != null ? serviceInfo.getDescription() : null);
}
/**
* @hide
*/
public boolean setDefaultServiceForCategory(ComponentName service, String category) {
- try {
- return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
- service, category);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(),
- service, category);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.setDefaultServiceForCategory(
+ mContext.getUser().getIdentifier(), service, category), false);
}
/**
* @hide
*/
public boolean setDefaultForNextTap(ComponentName service) {
- try {
- return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), service);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.setDefaultForNextTap(
+ mContext.getUser().getIdentifier(), service), false);
}
/**
* @hide
*/
public boolean setDefaultForNextTap(int userId, ComponentName service) {
- try {
- return sService.setDefaultForNextTap(userId, service);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.setDefaultForNextTap(userId, service);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.setDefaultForNextTap(userId, service), false);
}
/**
* @hide
*/
public List<ApduServiceInfo> getServices(String category) {
- try {
- return sService.getServices(mContext.getUser().getIdentifier(), category);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return null;
- }
- try {
- return sService.getServices(mContext.getUser().getIdentifier(), category);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return null;
- }
- }
+ return callServiceReturn(() ->
+ sService.getServices(
+ mContext.getUser().getIdentifier(), category), null);
}
/**
@@ -1117,22 +770,8 @@
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
@NonNull
public List<ApduServiceInfo> getServices(@NonNull String category, @UserIdInt int userId) {
- try {
- return sService.getServices(userId, category);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return null;
- }
- try {
- return sService.getServices(userId, category);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return null;
- }
- }
+ return callServiceReturn(() ->
+ sService.getServices(userId, category), null);
}
/**
@@ -1222,22 +861,8 @@
if (service == null) {
throw new NullPointerException("activity or service or category is null");
}
- try {
- return sService.setServiceEnabledForCategoryOther(userId, service, status);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.setServiceEnabledForCategoryOther(userId, service, status);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.setServiceEnabledForCategoryOther(userId, service, status), false);
}
/**
@@ -1269,22 +894,9 @@
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- try {
- return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
+ return callServiceReturn(() ->
+ sService.overrideRoutingTable(
+ mContext.getUser().getIdentifier(), protocol, technology), false);
}
/**
@@ -1303,27 +915,9 @@
if (!activity.isResumed()) {
throw new IllegalArgumentException("Activity must be resumed.");
}
- try {
- return sService.recoverRoutingTable(UserHandle.myUserId());
- } catch (RemoteException e) {
- // Try one more time
- recoverService();
- if (sService == null) {
- Log.e(TAG, "Failed to recover CardEmulationService.");
- return false;
- }
- try {
- return sService.recoverRoutingTable(UserHandle.myUserId());
- } catch (RemoteException ee) {
- Log.e(TAG, "Failed to reach CardEmulationService.");
- return false;
- }
- }
- }
-
- void recoverService() {
- NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
- sService = adapter.getCardEmulationService();
+ return callServiceReturn(() ->
+ sService.recoverRoutingTable(
+ mContext.getUser().getIdentifier()), false);
}
/**
@@ -1351,4 +945,53 @@
return ComponentName.unflattenFromString(defaultPaymentComponent);
}
+
+ /** @hide */
+ interface ServiceCall {
+ void call() throws RemoteException;
+ }
+ /** @hide */
+ public static void callService(ServiceCall call) {
+ try {
+ if (sService == null) {
+ NfcAdapter.attemptDeadServiceRecovery(
+ new RemoteException("NFC CardEmulation Service is null"));
+ sService = NfcAdapter.getCardEmulationService();
+ }
+ call.call();
+ } catch (RemoteException e) {
+ NfcAdapter.attemptDeadServiceRecovery(e);
+ sService = NfcAdapter.getCardEmulationService();
+ try {
+ call.call();
+ } catch (RemoteException ee) {
+ ee.rethrowAsRuntimeException();
+ }
+ }
+ }
+ /** @hide */
+ interface ServiceCallReturn<T> {
+ T call() throws RemoteException;
+ }
+ /** @hide */
+ public static <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) {
+ try {
+ if (sService == null) {
+ NfcAdapter.attemptDeadServiceRecovery(
+ new RemoteException("NFC CardEmulation Service is null"));
+ sService = NfcAdapter.getCardEmulationService();
+ }
+ return call.call();
+ } catch (RemoteException e) {
+ NfcAdapter.attemptDeadServiceRecovery(e);
+ sService = NfcAdapter.getCardEmulationService();
+ // Try one more time
+ try {
+ return call.call();
+ } catch (RemoteException ee) {
+ ee.rethrowAsRuntimeException();
+ }
+ }
+ return defaultReturn;
+ }
}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 95945d7..f16fa80 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -118,3 +118,10 @@
bug: "321310044"
}
+flag {
+ name: "nfc_action_manage_services_settings"
+ is_exported: true
+ namespace: "nfc"
+ description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS"
+ bug: "358129872"
+}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml
new file mode 100644
index 0000000..1e48443
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_base_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <include layout="@layout/non_collapsing_toolbar_content_layout"/>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
new file mode 100644
index 0000000..33519cb
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ android:outlineAmbientShadowColor="@android:color/transparent"
+ android:outlineSpotShadowColor="@android:color/transparent"
+ android:background="@android:color/transparent"
+ android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ android:transitionName="shared_element_view"
+ app:layout_collapseMode="pin"/>
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</merge>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
index 4659051..f46f110 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -171,7 +171,7 @@
private CollapsingToolbarDelegate getToolbarDelegate() {
if (mToolbardelegate == null) {
- mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+ mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback(), true);
}
return mToolbardelegate;
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 3965303..16ed5a8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -169,7 +169,7 @@
private CollapsingToolbarDelegate getToolbarDelegate() {
if (mToolbardelegate == null) {
- mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+ mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback(), true);
}
return mToolbardelegate;
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
index b605074..da97c30 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
@@ -57,7 +57,8 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
- mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+ mToolbardelegate =
+ new CollapsingToolbarDelegate(new DelegateCallback(), useCollapsingToolbar());
}
@Nullable
@@ -98,4 +99,8 @@
public FrameLayout getContentFrameLayout() {
return mToolbardelegate.getContentFrameLayout();
}
+
+ protected boolean useCollapsingToolbar() {
+ return true;
+ }
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index b633337..2ab2abd 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -21,6 +21,8 @@
import android.app.ActionBar;
import android.app.Activity;
import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
import android.util.Log;
@@ -80,8 +82,12 @@
@NonNull
private final HostCallback mHostCallback;
- public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback) {
+ private boolean mUseCollapsingToolbar;
+
+ public CollapsingToolbarDelegate(@NonNull HostCallback hostCallback,
+ boolean useCollapsingToolbar) {
mHostCallback = hostCallback;
+ mUseCollapsingToolbar = useCollapsingToolbar;
}
/** Method to call that creates the root view of the collapsing toolbar. */
@@ -94,13 +100,32 @@
@SuppressWarnings("RestrictTo")
View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
Activity activity) {
- final View view =
- inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
+ int layoutId;
+ boolean useCollapsingToolbar =
+ mUseCollapsingToolbar || Build.VERSION.SDK_INT < Build.VERSION_CODES.S;
+ if (useCollapsingToolbar) {
+ layoutId = R.layout.collapsing_toolbar_base_layout;
+ } else {
+ layoutId = R.layout.non_collapsing_toolbar_base_layout;
+ }
+ final View view = inflater.inflate(layoutId, container, false);
if (view instanceof CoordinatorLayout) {
mCoordinatorLayout = (CoordinatorLayout) view;
}
mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
mAppBarLayout = view.findViewById(R.id.app_bar);
+
+ if (!useCollapsingToolbar) {
+ // In the non-collapsing toolbar layout, we need to set the background of the app bar to
+ // the same as the activity background so that it covers the items extending above the
+ // bounds of the list for edge-to-edge.
+ TypedArray ta = container.getContext().obtainStyledAttributes(new int[] {
+ android.R.attr.windowBackground});
+ Drawable background = ta.getDrawable(0);
+ ta.recycle();
+ mAppBarLayout.setBackground(background);
+ }
+
if (mCollapsingToolbarLayout != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mCollapsingToolbarLayout.setLineSpacingMultiplier(TOOLBAR_LINE_SPACING_MULTIPLIER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
index d7147b5..0d73cb3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -22,8 +22,9 @@
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
-import androidx.compose.ui.test.assertIsOff
-import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
@@ -31,6 +32,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spa.testutils.waitUntilExists
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
@@ -99,8 +101,8 @@
setEntryItem(listModel)
- composeTestRule.onNodeWithText(context.getString(R.string.app_permission_summary_allowed))
- .assertIsDisplayed()
+ composeTestRule.waitUntilExists(
+ hasText(context.getString(R.string.app_permission_summary_allowed)))
}
@Test
@@ -141,8 +143,8 @@
setTogglePermissionAppInfoPage(listModel)
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
- .assertIsOn()
+ composeTestRule.waitUntilExists(
+ hasText(context.getString(listModel.switchTitleResId)) and isOn())
}
@Test
@@ -151,8 +153,8 @@
setTogglePermissionAppInfoPage(listModel)
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
- .assertIsOff()
+ composeTestRule.waitUntilExists(
+ hasText(context.getString(listModel.switchTitleResId)) and isOff())
}
@Test
@@ -160,11 +162,10 @@
val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = true)
setTogglePermissionAppInfoPage(listModel)
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
- .performClick()
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick()
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
- .assertIsOn()
+ composeTestRule.waitUntilExists(
+ hasText(context.getString(listModel.switchTitleResId)) and isOn())
}
@Test
@@ -172,11 +173,10 @@
val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)
setTogglePermissionAppInfoPage(listModel)
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
- .performClick()
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick()
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
- .assertIsOff()
+ composeTestRule.waitUntilExists(
+ hasText(context.getString(listModel.switchTitleResId)) and isOff())
}
@Test
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 754d9423..0d124e8 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -108,4 +108,14 @@
namespace: "android_settings"
description: "Settings catalyst project migration"
bug: "323791114"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "asha_profile_access_profile_enabled_true"
+ namespace: "accessibility"
+ description: "Changes the return value of HearingAidProfile.accessProfileEnabled() to true"
+ bug: "356530795"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 0fec61c..92da2be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1026,21 +1026,29 @@
return mDevice.getBluetoothClass();
}
+ /**
+ * Returns a list of {@link LocalBluetoothProfile} supported by the device.
+ */
public List<LocalBluetoothProfile> getProfiles() {
return new ArrayList<>(mProfiles);
}
- public List<LocalBluetoothProfile> getConnectableProfiles() {
- List<LocalBluetoothProfile> connectableProfiles =
- new ArrayList<LocalBluetoothProfile>();
+ /**
+ * Returns a list of {@link LocalBluetoothProfile} that are user-accessible from UI to
+ * initiate a connection.
+ *
+ * Note: Use {@link #getProfiles()} to retrieve all supported profiles on the device.
+ */
+ public List<LocalBluetoothProfile> getUiAccessibleProfiles() {
+ List<LocalBluetoothProfile> accessibleProfiles = new ArrayList<>();
synchronized (mProfileLock) {
for (LocalBluetoothProfile profile : mProfiles) {
if (profile.accessProfileEnabled()) {
- connectableProfiles.add(profile);
+ accessibleProfiles.add(profile);
}
}
}
- return connectableProfiles;
+ return accessibleProfiles;
}
public List<LocalBluetoothProfile> getRemovedProfiles() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index a49314a..7124ed2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -261,9 +261,9 @@
}
CachedBluetoothDevice dualModeDevice = groupDevicesList.stream()
- .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+ .filter(cachedDevice -> cachedDevice.getUiAccessibleProfiles().stream()
.anyMatch(profile -> profile instanceof LeAudioProfile))
- .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+ .filter(cachedDevice -> cachedDevice.getUiAccessibleProfiles().stream()
.anyMatch(profile -> profile instanceof A2dpProfile
|| profile instanceof HeadsetProfile))
.findFirst().orElse(null);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index f2450de..fb9b4df 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -34,6 +34,7 @@
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import com.android.settingslib.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -141,7 +142,7 @@
}
public boolean accessProfileEnabled() {
- return false;
+ return Flags.ashaProfileAccessProfileEnabledTrue();
}
public boolean isAutoConnectable() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 9ff5c43..326bb31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -19,37 +19,39 @@
import android.bluetooth.BluetoothAdapter
import android.content.Context
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
-import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
-import java.util.concurrent.ConcurrentHashMap
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
+import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.google.common.cache.CacheBuilder
+import com.google.common.cache.CacheLoader
+import com.google.common.cache.LoadingCache
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
/** Provides functionality to control bluetooth device settings. */
interface DeviceSettingRepository {
/** Gets config for the bluetooth device, returns null if failed. */
- suspend fun getDeviceSettingsConfig(cachedDevice: CachedBluetoothDevice): DeviceSettingsConfig?
-
- /** Gets all device settings for the bluetooth device. */
- fun getDeviceSettingList(
- cachedDevice: CachedBluetoothDevice,
- ): Flow<List<DeviceSetting>?>
+ suspend fun getDeviceSettingsConfig(
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceSettingConfigModel?
/** Gets device setting for the bluetooth device. */
fun getDeviceSetting(
cachedDevice: CachedBluetoothDevice,
@DeviceSettingId settingId: Int
- ): Flow<DeviceSetting?>
-
- /** Updates device setting for the bluetooth device. */
- suspend fun updateDeviceSettingState(
- cachedDevice: CachedBluetoothDevice,
- @DeviceSettingId deviceSettingId: Int,
- deviceSettingPreferenceState: DeviceSettingPreferenceState,
- )
+ ): Flow<DeviceSettingModel?>
}
class DeviceSettingRepositoryImpl(
@@ -58,40 +60,94 @@
private val coroutineScope: CoroutineScope,
private val backgroundCoroutineContext: CoroutineContext,
) : DeviceSettingRepository {
- private val deviceSettings =
- ConcurrentHashMap<CachedBluetoothDevice, DeviceSettingServiceConnection>()
+ private val connectionCache:
+ LoadingCache<CachedBluetoothDevice, DeviceSettingServiceConnection> =
+ CacheBuilder.newBuilder()
+ .weakValues()
+ .build(
+ object : CacheLoader<CachedBluetoothDevice, DeviceSettingServiceConnection>() {
+ override fun load(
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceSettingServiceConnection =
+ DeviceSettingServiceConnection(
+ cachedDevice,
+ context,
+ bluetoothAdaptor,
+ coroutineScope,
+ backgroundCoroutineContext,
+ )
+ }
+ )
override suspend fun getDeviceSettingsConfig(
cachedDevice: CachedBluetoothDevice
- ): DeviceSettingsConfig? = createConnectionIfAbsent(cachedDevice).getDeviceSettingsConfig()
-
- override fun getDeviceSettingList(
- cachedDevice: CachedBluetoothDevice
- ): Flow<List<DeviceSetting>?> = createConnectionIfAbsent(cachedDevice).getDeviceSettingList()
+ ): DeviceSettingConfigModel? =
+ connectionCache.get(cachedDevice).getDeviceSettingsConfig()?.toModel()
override fun getDeviceSetting(
cachedDevice: CachedBluetoothDevice,
settingId: Int
- ): Flow<DeviceSetting?> = createConnectionIfAbsent(cachedDevice).getDeviceSetting(settingId)
-
- override suspend fun updateDeviceSettingState(
- cachedDevice: CachedBluetoothDevice,
- @DeviceSettingId deviceSettingId: Int,
- deviceSettingPreferenceState: DeviceSettingPreferenceState,
- ) =
- createConnectionIfAbsent(cachedDevice)
- .updateDeviceSettings(deviceSettingId, deviceSettingPreferenceState)
-
- private fun createConnectionIfAbsent(
- cachedDevice: CachedBluetoothDevice
- ): DeviceSettingServiceConnection =
- deviceSettings.computeIfAbsent(cachedDevice) {
- DeviceSettingServiceConnection(
- cachedDevice,
- context,
- bluetoothAdaptor,
- coroutineScope,
- backgroundCoroutineContext,
- )
+ ): Flow<DeviceSettingModel?> =
+ connectionCache.get(cachedDevice).let { connection ->
+ connection.getDeviceSetting(settingId).map { it?.toModel(cachedDevice, connection) }
}
+
+ private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
+ DeviceSettingConfigModel(
+ mainItems = mainContentItems.map { it.toModel() },
+ moreSettingsItems = moreSettingsItems.map { it.toModel() },
+ moreSettingsPageFooter = moreSettingsFooter
+ )
+
+ private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel =
+ DeviceSettingConfigItemModel(settingId)
+
+ private fun DeviceSetting.toModel(
+ cachedDevice: CachedBluetoothDevice,
+ connection: DeviceSettingServiceConnection
+ ): DeviceSettingModel =
+ when (val pref = preference) {
+ is ActionSwitchPreference ->
+ DeviceSettingModel.ActionSwitchPreference(
+ cachedDevice = cachedDevice,
+ id = settingId,
+ title = pref.title,
+ summary = pref.summary,
+ icon = pref.icon,
+ isAllowedChangingState = pref.isAllowedChangingState,
+ intent = pref.intent,
+ switchState =
+ if (pref.hasSwitch()) {
+ DeviceSettingStateModel.ActionSwitchPreferenceState(pref.checked)
+ } else {
+ null
+ },
+ updateState = { newState ->
+ coroutineScope.launch(backgroundCoroutineContext) {
+ connection.updateDeviceSettings(
+ settingId,
+ newState.toParcelable(),
+ )
+ }
+ },
+ )
+ is MultiTogglePreference ->
+ DeviceSettingModel.MultiTogglePreference(
+ cachedDevice = cachedDevice,
+ id = settingId,
+ title = pref.title,
+ toggles = pref.toggleInfos.map { it.toModel() },
+ isAllowedChangingState = pref.isAllowedChangingState,
+ isActive = true,
+ state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state),
+ updateState = { newState ->
+ coroutineScope.launch(backgroundCoroutineContext) {
+ connection.updateDeviceSettings(settingId, newState.toParcelable())
+ }
+ },
+ )
+ else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
+ }
+
+ private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
new file mode 100644
index 0000000..cd597ee
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings.shared.model
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+
+/** Models a device setting config. */
+data class DeviceSettingConfigModel(
+ /** Items need to be shown in device details main page. */
+ val mainItems: List<DeviceSettingConfigItemModel>,
+ /** Items need to be shown in device details more settings page. */
+ val moreSettingsItems: List<DeviceSettingConfigItemModel>,
+ /** Footer text in more settings page. */
+ val moreSettingsPageFooter: String)
+
+/** Models a device setting item in config. */
+data class DeviceSettingConfigItemModel(
+ @DeviceSettingId val settingId: Int,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 72a60fb..fe6659d1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -634,7 +634,7 @@
}
private boolean isMediaDevice(CachedBluetoothDevice device) {
- for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
+ for (LocalBluetoothProfile profile : device.getUiAccessibleProfiles()) {
if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile ||
profile instanceof LeAudioProfile) {
return true;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index a06f084..f7492cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -16,8 +16,8 @@
package com.android.settingslib.notification.modes;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
@@ -40,13 +40,17 @@
private ZenModeConfig.ZenRule mConfigZenRule;
public static final ZenMode EXAMPLE = new TestModeBuilder().build();
- public static final ZenMode MANUAL_DND = ZenMode.manualDndMode(
- new AutomaticZenRule.Builder("Manual DND", Uri.parse("rule://dnd"))
- .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
- .build(),
- true /* isActive */
- );
+ public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY, true);
+ public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY, false);
+
+ public static ZenMode manualDnd(Uri conditionId, boolean isActive) {
+ return ZenMode.manualDndMode(
+ new AutomaticZenRule.Builder("Do Not Disturb", conditionId)
+ .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+ .build(),
+ isActive);
+ }
public TestModeBuilder() {
// Reasonable defaults
@@ -154,7 +158,7 @@
mRule.setEnabled(enabled);
mConfigZenRule.enabled = enabled;
if (!enabled) {
- mConfigZenRule.disabledOrigin = byUser ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_UNKNOWN;
+ mConfigZenRule.disabledOrigin = byUser ? ORIGIN_USER_IN_SYSTEMUI : ORIGIN_UNKNOWN;
}
return this;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 88af7ee..2f4b2ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
@@ -66,7 +67,7 @@
private static final String TAG = "ZenMode";
- static final String MANUAL_DND_MODE_ID = "manual_dnd";
+ static final String MANUAL_DND_MODE_ID = ZenModeConfig.MANUAL_RULE_ID;
static final String TEMP_NEW_MODE_ID = "temp_new_mode";
// Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
@@ -119,7 +120,7 @@
return Status.ENABLED;
}
} else {
- if (zenRuleExtraData.disabledOrigin == ZenModeConfig.UPDATE_ORIGIN_USER) {
+ if (zenRuleExtraData.disabledOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI) {
return Status.DISABLED_BY_USER;
} else {
return Status.DISABLED_BY_OTHER; // by APP, SYSTEM, UNKNOWN.
@@ -188,11 +189,37 @@
return mRule.getType();
}
+ /** Returns the trigger description of the mode. */
@Nullable
public String getTriggerDescription() {
return mRule.getTriggerDescription();
}
+ /**
+ * Returns a "dynamic" trigger description. For some modes (such as manual Do Not Disturb)
+ * when activated, we know when (and if) the mode is expected to end on its own; this dynamic
+ * description reflects that. In other cases, returns {@link #getTriggerDescription}.
+ */
+ @Nullable
+ public String getDynamicDescription(Context context) {
+ if (isManualDnd() && isActive()) {
+ long countdownEndTime = tryParseCountdownConditionId(mRule.getConditionId());
+ if (countdownEndTime > 0) {
+ CharSequence formattedTime = ZenModeConfig.getFormattedTime(context,
+ countdownEndTime, ZenModeConfig.isToday(countdownEndTime),
+ context.getUserId());
+ return context.getString(com.android.internal.R.string.zen_mode_until,
+ formattedTime);
+ }
+ }
+ // TODO: b/333527800 - For TYPE_SCHEDULE_TIME rules we could do the same; however
+ // according to the snoozing discussions the mode may or may not end at the scheduled
+ // time if manually activated. When we resolve that point, we could calculate end time
+ // for these modes as well.
+
+ return getTriggerDescription();
+ }
+
@NonNull
public ListenableFuture<Drawable> getIcon(@NonNull Context context,
@NonNull ZenIconLoader iconLoader) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index f533c95..492828d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -116,7 +116,6 @@
private ZenMode getManualDndMode(ZenModeConfig config) {
ZenModeConfig.ZenRule manualRule = config.manualRule;
- // TODO: b/333682392 - Replace with final strings for name & trigger description
AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder(
mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId)
.setType(manualRule.type)
@@ -127,7 +126,7 @@
.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
.build();
- return ZenMode.manualDndMode(manualDndRule, config != null && config.isManualActive());
+ return ZenMode.manualDndMode(manualDndRule, config.isManualActive());
}
public void updateMode(ZenMode mode) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index 3f59da4..f94f21f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -145,18 +145,18 @@
profiles.add(mHfpProfile);
profiles.add(mA2dpProfile);
profiles.add(mLeAudioProfile);
- when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice1.getUiAccessibleProfiles()).thenReturn(profiles);
when(mCachedDevice1.isConnected()).thenReturn(true);
profiles.clear();
profiles.add(mLeAudioProfile);
- when(mCachedDevice2.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice2.getUiAccessibleProfiles()).thenReturn(profiles);
when(mCachedDevice2.isConnected()).thenReturn(true);
profiles.clear();
profiles.add(mHfpProfile);
profiles.add(mA2dpProfile);
- when(mCachedDevice3.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice3.getUiAccessibleProfiles()).thenReturn(profiles);
when(mCachedDevice3.isConnected()).thenReturn(true);
}
@@ -253,7 +253,7 @@
when(mDevice2.isConnected()).thenReturn(false);
List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
profiles.add(mLeAudioProfile);
- when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice1.getUiAccessibleProfiles()).thenReturn(profiles);
CachedBluetoothDevice expectedDevice = mCachedDevice1;
assertThat(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index b5457c5..fee2394 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
+import android.graphics.Bitmap
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreferenceState
@@ -34,6 +35,14 @@
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
+import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreferenceState
+import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
@@ -148,7 +157,7 @@
val config = underTest.getDeviceSettingsConfig(cachedDevice)
- assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+ assertConfig(config!!, DEVICE_SETTING_CONFIG)
}
}
@@ -163,7 +172,7 @@
)
.thenReturn("".toByteArray())
- var config: DeviceSettingsConfig? = null
+ var config: DeviceSettingConfigModel? = null
val job = launch { config = underTest.getDeviceSettingsConfig(cachedDevice) }
delay(1000)
verify(bluetoothAdapter)
@@ -185,7 +194,7 @@
.thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
job.join()
- assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+ assertConfig(config!!, DEVICE_SETTING_CONFIG)
}
}
@@ -202,7 +211,7 @@
}
@Test
- fun getDeviceSettingList_success() {
+ fun getDeviceSetting_actionSwitchPreference_success() {
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -211,73 +220,7 @@
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
}
- `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
- input ->
- input
- .getArgument<IDeviceSettingsListener>(1)
- .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
- }
- var settings: List<DeviceSetting>? = null
-
- underTest
- .getDeviceSettingList(cachedDevice)
- .onEach { settings = it }
- .launchIn(backgroundScope)
- runCurrent()
-
- assertThat(settings?.map { it.settingId })
- .containsExactly(
- DeviceSettingId.DEVICE_SETTING_ID_HEADER,
- DeviceSettingId.DEVICE_SETTING_ID_ANC
- )
- assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
- .containsExactly(
- "title1",
- "title2",
- )
- }
- }
-
- @Test
- fun getDeviceSetting_oneServiceFailed_returnPartialResult() {
- testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
- input ->
- input
- .getArgument<IDeviceSettingsListener>(1)
- .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
- }
- var settings: List<DeviceSetting>? = null
-
- underTest
- .getDeviceSettingList(cachedDevice)
- .onEach { settings = it }
- .launchIn(backgroundScope)
- runCurrent()
-
- assertThat(settings?.map { it.settingId })
- .containsExactly(
- DeviceSettingId.DEVICE_SETTING_ID_HEADER,
- )
- assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
- .containsExactly(
- "title1",
- )
- }
- }
-
- @Test
- fun getDeviceSetting_success() {
- testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
- input ->
- input
- .getArgument<IDeviceSettingsListener>(1)
- .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
- }
- var setting: DeviceSetting? = null
+ var setting: DeviceSettingModel? = null
underTest
.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
@@ -285,13 +228,55 @@
.launchIn(backgroundScope)
runCurrent()
- assertThat(setting?.settingId).isEqualTo(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
- assertThat((setting?.preference as ActionSwitchPreference).title).isEqualTo("title1")
+ assertDeviceSetting(setting!!, DEVICE_SETTING_1)
}
}
@Test
- fun updateDeviceSetting_success() {
+ fun getDeviceSetting_multiTogglePreference_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
+ }
+ var setting: DeviceSettingModel? = null
+
+ underTest
+ .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)
+ .onEach { setting = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertDeviceSetting(setting!!, DEVICE_SETTING_2)
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_noConfig_returnNull() {
+ testScope.runTest {
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ var setting: DeviceSettingModel? = null
+
+ underTest
+ .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .onEach { setting = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(setting).isNull()
+ }
+ }
+
+ @Test
+ fun updateDeviceSettingState_switchState_success() {
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
@@ -300,12 +285,15 @@
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
}
+ var setting: DeviceSettingModel? = null
- underTest.updateDeviceSettingState(
- cachedDevice,
- DeviceSettingId.DEVICE_SETTING_ID_HEADER,
- ActionSwitchPreferenceState.Builder().build()
- )
+ underTest
+ .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .onEach { setting = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+ val updateFunc = (setting as DeviceSettingModel.ActionSwitchPreference).updateState!!
+ updateFunc(DeviceSettingStateModel.ActionSwitchPreferenceState(false))
runCurrent()
verify(settingProviderService1)
@@ -313,12 +301,107 @@
DEVICE_INFO,
DeviceSettingState.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
- .setPreferenceState(ActionSwitchPreferenceState.Builder().build())
+ .setPreferenceState(
+ ActionSwitchPreferenceState.Builder().setChecked(false).build()
+ )
.build()
)
}
}
+ @Test
+ fun updateDeviceSettingState_multiToggleState_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
+ }
+ var setting: DeviceSettingModel? = null
+
+ underTest
+ .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)
+ .onEach { setting = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+ val updateFunc = (setting as DeviceSettingModel.MultiTogglePreference).updateState
+ updateFunc(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+ runCurrent()
+
+ verify(settingProviderService2)
+ .updateDeviceSettings(
+ DEVICE_INFO,
+ DeviceSettingState.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
+ .setPreferenceState(
+ MultiTogglePreferenceState.Builder().setState(2).build()
+ )
+ .build()
+ )
+ }
+ }
+
+ private fun assertDeviceSetting(actual: DeviceSettingModel, serviceResponse: DeviceSetting) {
+ assertThat(actual.id).isEqualTo(serviceResponse.settingId)
+ when (actual) {
+ is DeviceSettingModel.ActionSwitchPreference -> {
+ assertThat(serviceResponse.preference)
+ .isInstanceOf(ActionSwitchPreference::class.java)
+ val pref = serviceResponse.preference as ActionSwitchPreference
+ assertThat(actual.title).isEqualTo(pref.title)
+ assertThat(actual.summary).isEqualTo(pref.summary)
+ assertThat(actual.icon).isEqualTo(pref.icon)
+ assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
+ if (pref.hasSwitch()) {
+ assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
+ } else {
+ assertThat(actual.switchState).isNull()
+ }
+ }
+ is DeviceSettingModel.MultiTogglePreference -> {
+ assertThat(serviceResponse.preference)
+ .isInstanceOf(MultiTogglePreference::class.java)
+ val pref = serviceResponse.preference as MultiTogglePreference
+ assertThat(actual.title).isEqualTo(pref.title)
+ assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
+ assertThat(actual.toggles.size).isEqualTo(pref.toggleInfos.size)
+ for (i in 0..<actual.toggles.size) {
+ assertToggle(actual.toggles[i], pref.toggleInfos[i])
+ }
+ }
+ else -> {}
+ }
+ }
+
+ private fun assertToggle(actual: ToggleModel, serviceResponse: ToggleInfo) {
+ assertThat(actual.label).isEqualTo(serviceResponse.label)
+ assertThat(actual.icon).isEqualTo(serviceResponse.icon)
+ }
+
+ private fun assertConfig(
+ actual: DeviceSettingConfigModel,
+ serviceResponse: DeviceSettingsConfig
+ ) {
+ assertThat(actual.mainItems.size).isEqualTo(serviceResponse.mainContentItems.size)
+ for (i in 0..<actual.mainItems.size) {
+ assertConfigItem(actual.mainItems[i], serviceResponse.mainContentItems[i])
+ }
+ assertThat(actual.moreSettingsItems.size).isEqualTo(serviceResponse.moreSettingsItems.size)
+ for (i in 0..<actual.moreSettingsItems.size) {
+ assertConfigItem(actual.moreSettingsItems[i], serviceResponse.moreSettingsItems[i])
+ }
+ assertThat(actual.moreSettingsPageFooter).isEqualTo(serviceResponse.moreSettingsFooter)
+ }
+
+ private fun assertConfigItem(
+ actual: DeviceSettingConfigItemModel,
+ serviceResponse: DeviceSettingItem
+ ) {
+ assertThat(actual.settingId).isEqualTo(serviceResponse.settingId)
+ }
+
private companion object {
const val BLUETOOTH_ADDRESS = "12:34:56:78"
const val CONFIG_SERVICE_PACKAGE_NAME = "com.android.fake.configservice"
@@ -377,10 +460,21 @@
DeviceSetting.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
.setPreference(
- ActionSwitchPreference.Builder()
- .setTitle("title2")
- .setHasSwitch(true)
- .setAllowedChangingState(true)
+ MultiTogglePreference.Builder()
+ .setTitle("title1")
+ .setAllowChangingState(true)
+ .addToggleInfo(
+ ToggleInfo.Builder()
+ .setLabel("label1")
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .build()
+ )
+ .addToggleInfo(
+ ToggleInfo.Builder()
+ .setLabel("label2")
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .build()
+ )
.build()
)
.build()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index a30d6a7..3e8457b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -470,7 +470,7 @@
when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
- when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
+ when(cachedDevice.getUiAccessibleProfiles()).thenReturn(profiles);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
when(cachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index 651e57c..d9fdcc38 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -93,7 +93,7 @@
public void constructor_disabledRuleByUser_statusDisabledByUser() {
AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(false).build();
ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false);
- configZenRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER;
+ configZenRule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
ZenMode mode = new ZenMode("id", azr, configZenRule);
assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.DISABLED_BY_USER);
@@ -103,7 +103,7 @@
public void constructor_disabledRuleByOther_statusDisabledByOther() {
AutomaticZenRule azr = new AutomaticZenRule.Builder(ZEN_RULE).setEnabled(false).build();
ZenModeConfig.ZenRule configZenRule = zenConfigRuleFor(azr, false);
- configZenRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP;
+ configZenRule.disabledOrigin = ZenModeConfig.ORIGIN_APP;
ZenMode mode = new ZenMode("id", azr, configZenRule);
assertThat(mode.getStatus()).isEqualTo(ZenMode.Status.DISABLED_BY_OTHER);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 03c2a83..65937ea 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -445,7 +445,6 @@
String.valueOf(Global.Wearable.TETHERED_CONFIG_TETHERED),
String.valueOf(Global.Wearable.TETHERED_CONFIG_RESTRICTED)
}));
- VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
@@ -457,5 +456,10 @@
VALIDATORS.put(Global.ADD_USERS_WHEN_LOCKED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.REMOVE_GUEST_ON_EXIT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.USER_SWITCHER_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE,
+ new InclusiveIntegerRangeValidator(
+ Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_NONE,
+ Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE_COMPANION
+ ));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index f53dec6..b1e6d66 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -28,6 +28,13 @@
}
flag {
+ name: "use_new_storage_value"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, read the new storage value in aconfig codegen, and actually use it."
+ bug: "312235596"
+}
+
+flag {
name: "load_apex_aconfig_protobufs"
namespace: "core_experiments_team_internal"
description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8c96484..d39b564 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -629,11 +629,11 @@
Settings.Global.Wearable.CUSTOM_COLOR_BACKGROUND,
Settings.Global.Wearable.PHONE_SWITCHING_STATUS,
Settings.Global.Wearable.TETHER_CONFIG_STATE,
- Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED,
Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE,
Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE,
Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED,
- Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON);
+ Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON,
+ Settings.Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
@@ -677,6 +677,7 @@
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
Settings.Secure.CARRIER_APPS_HANDLED,
Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
+ Settings.Secure.COMPAT_UI_EDUCATION_SHOWING,
Settings.Secure.COMPLETED_CATEGORY_PREFIX,
Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
Settings.Secure.CONTENT_CAPTURE_ENABLED,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c2e8c37..6d78705 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -645,6 +645,15 @@
}
filegroup {
+ name: "SystemUI-robotest-utils",
+ srcs: [
+ "tests/robolectric/src/com/android/systemui/testutils/**/*.kt",
+ "tests/robolectric/src/com/android/systemui/testutils/**/*.java",
+ ],
+ path: "tests/robolectric/src/com/android/systemui/testutils",
+}
+
+filegroup {
name: "SystemUI-tests-multivalent",
srcs: [
"multivalentTests/src/**/*.kt",
@@ -944,35 +953,36 @@
strict_mode: false,
}
-android_ravenwood_test {
- name: "SystemUiRavenTests",
- srcs: [
- ":SystemUI-tests-utils",
- ":SystemUI-tests-multivalent",
- // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
- // use_resource_processor: true when better supported by soong
- ":SystemUIRobo-stub{.aapt.srcjar}",
- ],
- static_libs: [
- "SystemUI-core",
- "SystemUI-res",
- "SystemUI-tests-base",
- "androidx.test.uiautomator_uiautomator",
- "androidx.core_core-animation-testing",
- "androidx.test.ext.junit",
- "kosmos",
- "mockito-kotlin-nodeps",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- "android.test.mock",
- ],
- auto_gen_config: true,
- plugins: [
- "dagger2-compiler",
- ],
-}
+// Disable for now. TODO(b/356666754) Re-enable it
+// android_ravenwood_test {
+// name: "SystemUiRavenTests",
+// srcs: [
+// ":SystemUI-tests-utils",
+// ":SystemUI-tests-multivalent",
+// // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
+// // use_resource_processor: true when better supported by soong
+// ":SystemUIRobo-stub{.aapt.srcjar}",
+// ],
+// static_libs: [
+// "SystemUI-core",
+// "SystemUI-res",
+// "SystemUI-tests-base",
+// "androidx.test.uiautomator_uiautomator",
+// "androidx.core_core-animation-testing",
+// "androidx.test.ext.junit",
+// "kosmos",
+// "mockito-kotlin-nodeps",
+// ],
+// libs: [
+// "android.test.runner",
+// "android.test.base",
+// "android.test.mock",
+// ],
+// auto_gen_config: true,
+// plugins: [
+// "dagger2-compiler",
+// ],
+// }
// Opt-out config for optimizing the SystemUI target using R8.
// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
index 66a2fae..c698d18 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/model/A11yMenuShortcut.java
@@ -19,7 +19,6 @@
import com.android.systemui.accessibility.accessibilitymenu.R;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -52,80 +51,80 @@
private static final int LABEL_TEXT_INDEX = 3;
/** Map stores all shortcut resource IDs that is in matching order of defined shortcut. */
- private static final Map<ShortcutId, int[]> sShortcutResource = new HashMap<>() {{
- put(ShortcutId.ID_ASSISTANT_VALUE, new int[] {
+ private static final Map<ShortcutId, int[]> sShortcutResource = Map.ofEntries(
+ Map.entry(ShortcutId.ID_ASSISTANT_VALUE, new int[] {
R.drawable.ic_logo_a11y_assistant_24dp,
R.color.assistant_color,
R.string.assistant_utterance,
R.string.assistant_label,
- });
- put(ShortcutId.ID_A11YSETTING_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_A11YSETTING_VALUE, new int[] {
R.drawable.ic_logo_a11y_settings_24dp,
R.color.a11y_settings_color,
R.string.a11y_settings_label,
R.string.a11y_settings_label,
- });
- put(ShortcutId.ID_POWER_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_POWER_VALUE, new int[] {
R.drawable.ic_logo_a11y_power_24dp,
R.color.power_color,
R.string.power_utterance,
R.string.power_label,
- });
- put(ShortcutId.ID_RECENT_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_RECENT_VALUE, new int[] {
R.drawable.ic_logo_a11y_recent_apps_24dp,
R.color.recent_apps_color,
R.string.recent_apps_label,
R.string.recent_apps_label,
- });
- put(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] {
R.drawable.ic_logo_a11y_lock_24dp,
R.color.lockscreen_color,
R.string.lockscreen_label,
R.string.lockscreen_label,
- });
- put(ShortcutId.ID_QUICKSETTING_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_QUICKSETTING_VALUE, new int[] {
R.drawable.ic_logo_a11y_quick_settings_24dp,
R.color.quick_settings_color,
R.string.quick_settings_label,
R.string.quick_settings_label,
- });
- put(ShortcutId.ID_NOTIFICATION_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_NOTIFICATION_VALUE, new int[] {
R.drawable.ic_logo_a11y_notifications_24dp,
R.color.notifications_color,
R.string.notifications_label,
R.string.notifications_label,
- });
- put(ShortcutId.ID_SCREENSHOT_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_SCREENSHOT_VALUE, new int[] {
R.drawable.ic_logo_a11y_screenshot_24dp,
R.color.screenshot_color,
R.string.screenshot_utterance,
R.string.screenshot_label,
- });
- put(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] {
R.drawable.ic_logo_a11y_brightness_up_24dp,
R.color.brightness_color,
R.string.brightness_up_label,
R.string.brightness_up_label,
- });
- put(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] {
R.drawable.ic_logo_a11y_brightness_down_24dp,
R.color.brightness_color,
R.string.brightness_down_label,
R.string.brightness_down_label,
- });
- put(ShortcutId.ID_VOLUME_UP_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_VOLUME_UP_VALUE, new int[] {
R.drawable.ic_logo_a11y_volume_up_24dp,
R.color.volume_color,
R.string.volume_up_label,
R.string.volume_up_label,
- });
- put(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] {
+ }),
+ Map.entry(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] {
R.drawable.ic_logo_a11y_volume_down_24dp,
R.color.volume_color,
R.string.volume_down_label,
R.string.volume_down_label,
- });
- }};
+ })
+ );
/** Shortcut id used to identify. */
private int mShortcutId = ShortcutId.UNSPECIFIED_ID_VALUE.ordinal();
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 23c6098..e6fae7b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -475,18 +475,6 @@
}
flag {
- name: "centralized_status_bar_height_fix"
- namespace: "systemui"
- description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
- " central place, instead of reading resources directly. This is to take into account display"
- " cutouts and other special cases. "
- bug: "317016114"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "enable_layout_tracing"
namespace: "systemui"
description: "Enables detailed traversal slices during measure and layout in perfetto traces"
@@ -1209,6 +1197,17 @@
}
flag {
+ name: "hubmode_fullscreen_vertical_swipe_fix"
+ namespace: "systemui"
+ description: "Bug fix that enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+ bug: "340177049"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
+flag {
namespace: "systemui"
name: "remove_update_listener_in_qs_icon_view_impl"
description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt
new file mode 100644
index 0000000..d8c7c06
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetector.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+/**
+ * Checks if the synchronous APIs like registerContentObserverSync/unregisterContentObserverSync are
+ * invoked for SettingsProxy or it's sub-classes, and raise a warning notifying the caller to use
+ * the asynchronous/suspend APIs instead.
+ */
+@Suppress("UnstableApiUsage")
+class RegisterContentObserverSyncViaSettingsProxyDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return SYNC_METHOD_LIST
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+
+ val evaluator = context.evaluator
+ if (evaluator.isMemberInSubClassOf(method, SETTINGS_PROXY_CLASS)) {
+ context.report(
+ issue = SYNC_WARNING,
+ location = context.getNameLocation(node),
+ message =
+ "`Avoid using ${method.name}()` if calling the API is not " +
+ "required on the main thread. Instead use an appropriate async interface " +
+ "API call for eg. `registerContentObserver()` or " +
+ "`registerContentObserverAsync()`."
+ )
+ }
+ }
+
+ companion object {
+ val SYNC_WARNING: Issue =
+ Issue.create(
+ id = "RegisterContentObserverSyncWarning",
+ briefDescription =
+ "Synchronous content observer registration API called " +
+ "instead of the async APIs.`",
+ // lint trims indents and converts \ to line continuations
+ explanation =
+ """
+ ContentObserver registration/de-registration done via \
+ `SettingsProxy.registerContentObserverSync` will block the main thread \
+ and may cause missed frames. Instead, use \
+ `SettingsProxy.registerContentObserver()` or \
+ `SettingsProxy.registerContentObserverAsync()`. These APIs will ensure \
+ that the registrations/de-registrations happen sequentially on a
+ background worker thread.""",
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ RegisterContentObserverSyncViaSettingsProxyDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ private val SYNC_METHOD_LIST =
+ listOf(
+ "registerContentObserverSync",
+ "unregisterContentObserverSync",
+ "registerContentObserverForUserSync"
+ )
+
+ private val SETTINGS_PROXY_CLASS = "com.android.systemui.util.settings.SettingsProxy"
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 73ac6cc..5206b05 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -46,10 +46,12 @@
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
MissingApacheLicenseDetector.ISSUE,
+ RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING
)
override val api: Int
get() = CURRENT_API
+
override val minApi: Int
get() = 8
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt
new file mode 100644
index 0000000..57347d3
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverSyncViaSettingsProxyDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+/** Test class for [RegisterContentObserverSyncViaSettingsProxyDetector]. */
+class RegisterContentObserverSyncViaSettingsProxyDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector = RegisterContentObserverSyncViaSettingsProxyDetector()
+
+ override fun getIssues(): List<Issue> =
+ listOf(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+
+ @Test
+ fun testRegisterContentObserverSync_throwError() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import com.android.systemui.util.settings.SecureSettings;
+ public class TestClass {
+ public void register(SecureSettings secureSettings) {
+ secureSettings.
+ registerContentObserverSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mSettingObserver);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Avoid using registerContentObserverSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+ registerContentObserverSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ .trimIndent()
+ )
+ }
+
+ @Test
+ fun testRegisterContentObserverForUserSync_throwError() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import com.android.systemui.util.settings.SecureSettings;
+ public class TestClass {
+ public void register(SecureSettings secureSettings) {
+ secureSettings.
+ registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mSettingObserver);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Avoid using registerContentObserverForUserSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+ registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ .trimIndent()
+ )
+ }
+
+ @Test
+ fun testSuppressRegisterContentObserverSync() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import com.android.systemui.util.settings.SecureSettings;
+ public class TestClass {
+ @SuppressWarnings("RegisterContentObserverSyncWarning")
+ public void register(SecureSettings secureSettings) {
+ secureSettings.
+ registerContentObserverForUserSync(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mSettingObserver);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testNoopIfNoCall() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import com.android.systemui.util.settings.SecureSettings;
+ public class TestClass {
+ public void register(SecureSettings secureSettings) {
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testUnRegisterContentObserverSync_throwError() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import com.android.systemui.util.settings.SecureSettings;
+ public class TestClass {
+ public void register(SecureSettings secureSettings) {
+ secureSettings.
+ unregisterContentObserverSync(mSettingObserver);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Avoid using unregisterContentObserverSync() if calling the API is not required on the main thread. Instead use an appropriate async interface API call for eg. registerContentObserver() or registerContentObserverAsync(). [RegisterContentObserverSyncWarning]
+ unregisterContentObserverSync(mSettingObserver);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+ """
+ .trimIndent()
+ )
+ }
+
+ private companion object {
+ private val SETTINGS_PROXY_STUB =
+ kotlin(
+ """
+ package com.android.systemui.util.settings
+ interface SettingsProxy {
+ fun registerContentObserverSync() {}
+ fun unregisterContentObserverSync() {}
+ }
+ """
+ )
+ .indented()
+
+ private val USER_SETTINGS_PROXY_STUB =
+ kotlin(
+ """
+ package com.android.systemui.util.settings
+ interface UserSettingsProxy : SettingsProxy {
+ fun registerContentObserverForUserSync() {}
+ }
+ """
+ )
+ .indented()
+
+ private val SECURE_SETTINGS_STUB =
+ kotlin(
+ """
+ package com.android.systemui.util.settings
+ interface SecureSettings : UserSettingsProxy {}
+ """
+ )
+ .indented()
+ }
+
+ private val stubs = arrayOf(SETTINGS_PROXY_STUB, USER_SETTINGS_PROXY_STUB, SECURE_SETTINGS_STUB)
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index bd5b795a..72965fb 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -66,11 +66,11 @@
@Provides
fun providesLockscreenContent(
- viewModel: LockscreenContentViewModel,
+ viewModelFactory: LockscreenContentViewModel.Factory,
blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
clockInteractor: KeyguardClockInteractor,
): LockscreenContent {
- return LockscreenContent(viewModel, blueprints, clockInteractor)
+ return LockscreenContent(viewModelFactory, blueprints, clockInteractor)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 69f1174..b65b471 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -160,6 +160,7 @@
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.internal.R.dimen.system_app_widget_background_radius
+import com.android.systemui.Flags
import com.android.systemui.Flags.communalTimerFlickerFix
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
@@ -269,7 +270,7 @@
}
}
// Nested scroll for full screen swipe to get to shade and bouncer
- .thenIf(!viewModel.isEditMode) {
+ .thenIf(!viewModel.isEditMode && Flags.hubmodeFullscreenVerticalSwipeFix()) {
Modifier.nestedScroll(nestedScrollConnection).pointerInput(viewModel) {
awaitPointerEventScope {
while (true) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 25e91be..672b8a7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.lifecycle.rememberViewModel
/**
* Renders the content of the lockscreen.
@@ -37,7 +38,7 @@
* outside the scene container framework.
*/
class LockscreenContent(
- private val viewModel: LockscreenContentViewModel,
+ private val viewModelFactory: LockscreenContentViewModel.Factory,
private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
private val clockInteractor: KeyguardClockInteractor,
) {
@@ -49,6 +50,7 @@
fun SceneScope.Content(
modifier: Modifier = Modifier,
) {
+ val viewModel = rememberViewModel { viewModelFactory.create() }
val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
if (!isContentVisible) {
// If the content isn't supposed to be visible, show a large empty box as it's needed
@@ -69,6 +71,6 @@
}
val blueprint = blueprintByBlueprintId[blueprintId] ?: return
- with(blueprint) { Content(modifier.sysuiResTag("keyguard_root_view")) }
+ with(blueprint) { Content(viewModel, modifier.sysuiResTag("keyguard_root_view")) }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index b077e18..7fe1b3e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -24,7 +24,7 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -37,13 +37,21 @@
class LockscreenScene
@Inject
constructor(
- viewModel: LockscreenSceneViewModel,
+ actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = Scenes.Lockscreen
+ private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- viewModel.destinationScenes
+ actionsViewModel.actions
+
+ override suspend fun activate() {
+ actionsViewModel.activate()
+ }
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 9afb4d5..a78c038 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.composable
import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
-import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
import dagger.Module
@@ -26,7 +25,6 @@
[
CommunalBlueprintModule::class,
OptionalSectionModule::class,
- ShortcutsBesideUdfpsBlueprintModule::class,
],
)
interface LockscreenSceneBlueprintModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 210ca69..adad446 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -32,16 +32,15 @@
import javax.inject.Inject
/** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint
-@Inject
-constructor(
- private val viewModel: LockscreenContentViewModel,
-) : ComposableLockscreenSceneBlueprint {
+class CommunalBlueprint @Inject constructor() : ComposableLockscreenSceneBlueprint {
override val id: String = "communal"
@Composable
- override fun SceneScope.Content(modifier: Modifier) {
+ override fun SceneScope.Content(
+ viewModel: LockscreenContentViewModel,
+ modifier: Modifier,
+ ) {
LockscreenLongPress(
viewModel = viewModel.touchHandling,
modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
index cb73983..df36d07 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
@@ -20,9 +20,14 @@
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
/** Defines interface for classes that can render the content for a specific blueprint/layout. */
interface ComposableLockscreenSceneBlueprint : LockscreenSceneBlueprint {
/** Renders the content of this blueprint. */
- @Composable fun SceneScope.Content(modifier: Modifier)
+ @Composable
+ fun SceneScope.Content(
+ viewModel: LockscreenContentViewModel,
+ modifier: Modifier,
+ )
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index a9e63c6..a3e0701 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -53,7 +53,6 @@
class DefaultBlueprint
@Inject
constructor(
- private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
@@ -66,10 +65,17 @@
override val id: String = "default"
@Composable
- override fun SceneScope.Content(modifier: Modifier) {
+ override fun SceneScope.Content(
+ viewModel: LockscreenContentViewModel,
+ modifier: Modifier,
+ ) {
val isUdfpsVisible = viewModel.isUdfpsVisible
val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
+ val areNotificationsVisible by
+ viewModel
+ .areNotificationsVisible(contentKey)
+ .collectAsStateWithLifecycle(initialValue = false)
LockscreenLongPress(
viewModel = viewModel.touchHandling,
@@ -94,6 +100,7 @@
Box {
with(topAreaSection) {
DefaultClockLayout(
+ smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop,
modifier =
Modifier.thenIf(isShadeLayoutWide) {
Modifier.fillMaxWidth(0.5f)
@@ -106,6 +113,8 @@
if (isShadeLayoutWide) {
with(notificationSection) {
Notifications(
+ areNotificationsVisible = areNotificationsVisible,
+ isShadeLayoutWide = isShadeLayoutWide,
burnInParams = null,
modifier =
Modifier.fillMaxWidth(0.5f)
@@ -118,6 +127,8 @@
if (!isShadeLayoutWide) {
with(notificationSection) {
Notifications(
+ areNotificationsVisible = areNotificationsVisible,
+ isShadeLayoutWide = isShadeLayoutWide,
burnInParams = null,
modifier = Modifier.weight(weight = 1f)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
deleted file mode 100644
index 72cf832..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ /dev/null
@@ -1,248 +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.keyguard.ui.composable.blueprint
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.unit.IntRect
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
-import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
-import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
-import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.keyguard.ui.composable.section.NotificationSection
-import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
-import com.android.systemui.keyguard.ui.composable.section.TopAreaSection
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.IntoSet
-import java.util.Optional
-import javax.inject.Inject
-import kotlin.math.roundToInt
-
-/**
- * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
- * factor).
- */
-class ShortcutsBesideUdfpsBlueprint
-@Inject
-constructor(
- private val viewModel: LockscreenContentViewModel,
- private val statusBarSection: StatusBarSection,
- private val lockSection: LockSection,
- private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
- private val bottomAreaSection: BottomAreaSection,
- private val settingsMenuSection: SettingsMenuSection,
- private val topAreaSection: TopAreaSection,
- private val notificationSection: NotificationSection,
-) : ComposableLockscreenSceneBlueprint {
-
- override val id: String = "shortcuts-besides-udfps"
-
- @Composable
- override fun SceneScope.Content(modifier: Modifier) {
- val isUdfpsVisible = viewModel.isUdfpsVisible
- val isShadeLayoutWide by viewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
- val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
-
- LockscreenLongPress(
- viewModel = viewModel.touchHandling,
- modifier = modifier,
- ) { onSettingsMenuPlaced ->
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxSize(),
- ) {
- with(statusBarSection) {
- StatusBar(
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- horizontal = { unfoldTranslations.start.roundToInt() },
- )
- )
- }
-
- Box {
- with(topAreaSection) {
- DefaultClockLayout(
- modifier =
- Modifier.graphicsLayer {
- translationX = unfoldTranslations.start
- },
- )
- }
- if (isShadeLayoutWide) {
- with(notificationSection) {
- Notifications(
- burnInParams = null,
- modifier =
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
- )
- }
- }
- }
- if (!isShadeLayoutWide) {
- with(notificationSection) {
- Notifications(
- burnInParams = null,
- modifier = Modifier.weight(weight = 1f)
- )
- }
- }
- if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
- with(ambientIndicationSectionOptional.get()) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
- }
-
- // Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) {
- Shortcut(
- isStart = true,
- applyPadding = false,
- modifier =
- Modifier.graphicsLayer { translationX = unfoldTranslations.start },
- )
- }
-
- with(lockSection) { LockIcon() }
-
- // Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) {
- Shortcut(
- isStart = false,
- applyPadding = false,
- modifier =
- Modifier.graphicsLayer { translationX = unfoldTranslations.end },
- )
- }
-
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
- with(ambientIndicationSectionOptional.get()) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
- }
- }
-
- with(bottomAreaSection) {
- IndicationArea(modifier = Modifier.fillMaxWidth())
- }
- }
-
- // Aligned to bottom and NOT constrained by the lock icon.
- with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
- },
- modifier = Modifier.fillMaxSize(),
- ) { measurables, constraints ->
- check(measurables.size == 6)
- val aboveLockIconMeasurable = measurables[0]
- val startSideShortcutMeasurable = measurables[1]
- val lockIconMeasurable = measurables[2]
- val endSideShortcutMeasurable = measurables[3]
- val belowLockIconMeasurable = measurables[4]
- val settingsMenuMeasurable = measurables[5]
-
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
-
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
-
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val startSideShortcutPlaceable =
- startSideShortcutMeasurable.measure(noMinConstraints)
- val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(
- maxHeight = constraints.maxHeight - lockIconBounds.bottom
- )
- )
- val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
-
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- startSideShortcutPlaceable.placeRelative(
- x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- endSideShortcutPlaceable.placeRelative(
- x =
- lockIconBounds.right +
- (constraints.maxWidth - lockIconBounds.right) / 2 -
- endSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
- settingsMenuPlaceable.place(
- x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
- y = constraints.maxHeight - settingsMenuPlaceable.height,
- )
- }
- }
- }
- }
-}
-
-@Module
-interface ShortcutsBesideUdfpsBlueprintModule {
- @Binds
- @IntoSet
- fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): ComposableLockscreenSceneBlueprint
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 9c72d93..364adca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -32,7 +32,6 @@
import androidx.core.content.res.ResourcesCompat
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -40,10 +39,8 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.Flow
@@ -52,11 +49,9 @@
@Inject
constructor(
private val viewModel: KeyguardQuickAffordancesCombinedViewModel,
- private val falsingManager: FalsingManager,
- private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val shortcutsLogger: KeyguardQuickAffordancesLogger,
+ private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
) {
/**
* Renders a single lockscreen shortcut.
@@ -80,9 +75,8 @@
viewId = if (isStart) R.id.start_button else R.id.end_button,
viewModel = if (isStart) viewModel.startButton else viewModel.endButton,
transitionAlpha = viewModel.transitionAlpha,
- falsingManager = falsingManager,
- vibratorHelper = vibratorHelper,
indicationController = indicationController,
+ binder = keyguardQuickAffordanceViewBinder,
modifier =
if (applyPadding) {
Modifier.shortcutPadding()
@@ -124,9 +118,8 @@
@IdRes viewId: Int,
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
transitionAlpha: Flow<Float>,
- falsingManager: FalsingManager,
- vibratorHelper: VibratorHelper,
indicationController: KeyguardIndicationController,
+ binder: KeyguardQuickAffordanceViewBinder,
modifier: Modifier = Modifier,
) {
val (binding, setBinding) = mutableStateOf<KeyguardQuickAffordanceViewBinder.Binding?>(null)
@@ -158,13 +151,10 @@
}
setBinding(
- KeyguardQuickAffordanceViewBinder.bind(
+ binder.bind(
view,
viewModel,
transitionAlpha,
- falsingManager,
- vibratorHelper,
- shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index df068c4..4e117d6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -23,21 +23,16 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.thenIf
-import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
-import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -59,7 +54,6 @@
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
- private val lockscreenContentViewModel: LockscreenContentViewModel,
) {
init {
@@ -89,23 +83,18 @@
* adjustment
*/
@Composable
- fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
- val areNotificationsVisible by
- lockscreenContentViewModel
- .areNotificationsVisible(contentKey)
- .collectAsStateWithLifecycle(initialValue = false)
+ fun SceneScope.Notifications(
+ areNotificationsVisible: Boolean,
+ isShadeLayoutWide: Boolean,
+ burnInParams: BurnInParameters?,
+ modifier: Modifier = Modifier
+ ) {
if (!areNotificationsVisible) {
return
}
- val isShadeLayoutWide by
- lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
val splitShadeTopMargin: Dp =
- if (Flags.centralizedStatusBarHeightFix()) {
- LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
- } else {
- dimensionResource(id = R.dimen.large_screen_shade_header_height)
- }
+ LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 33ed14b..da78eed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.content.res.Resources
import android.widget.FrameLayout
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
@@ -43,7 +44,6 @@
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
@@ -55,12 +55,12 @@
private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
private val aodBurnInViewModel: AodBurnInViewModel,
- private val lockscreenContentViewModel: LockscreenContentViewModel,
) {
@Composable
fun SceneScope.SmartSpace(
burnInParams: BurnInParameters,
onTopChanged: (top: Float?) -> Unit,
+ smartSpacePaddingTop: (Resources) -> Int,
modifier: Modifier = Modifier,
) {
val resources = LocalContext.current.resources
@@ -72,9 +72,7 @@
modifier
.onTopPlacementChanged(onTopChanged)
.padding(
- top = {
- lockscreenContentViewModel.getSmartSpacePaddingTop(resources)
- },
+ top = { smartSpacePaddingTop(resources) },
bottom = {
resources.getDimensionPixelSize(
R.dimen.keyguard_status_view_bottom_margin
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index c0832d9..0eeb79b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.composable.section
import android.content.Context
+import android.content.res.Resources
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -61,6 +62,7 @@
) {
@Composable
fun SceneScope.DefaultClockLayout(
+ smartSpacePaddingTop: (Resources) -> Int,
modifier: Modifier = Modifier,
) {
val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle()
@@ -99,22 +101,41 @@
SceneTransitionLayout(state) {
scene(splitShadeLargeClockScene) {
LargeClockWithSmartSpace(
+ smartSpacePaddingTop = smartSpacePaddingTop,
shouldOffSetClockToOneHalf = !hasCustomPositionUpdatedAnimation
)
}
scene(splitShadeSmallClockScene) {
- SmallClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+ SmallClockWithSmartSpace(
+ smartSpacePaddingTop = smartSpacePaddingTop,
+ modifier = Modifier.fillMaxWidth(0.5f),
+ )
}
- scene(smallClockScene) { SmallClockWithSmartSpace() }
+ scene(smallClockScene) {
+ SmallClockWithSmartSpace(
+ smartSpacePaddingTop = smartSpacePaddingTop,
+ )
+ }
- scene(largeClockScene) { LargeClockWithSmartSpace() }
+ scene(largeClockScene) {
+ LargeClockWithSmartSpace(
+ smartSpacePaddingTop = smartSpacePaddingTop,
+ )
+ }
- scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() }
+ scene(WeatherClockScenes.largeClockScene) {
+ WeatherLargeClockWithSmartSpace(
+ smartSpacePaddingTop = smartSpacePaddingTop,
+ )
+ }
scene(WeatherClockScenes.splitShadeLargeClockScene) {
- WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f))
+ WeatherLargeClockWithSmartSpace(
+ smartSpacePaddingTop = smartSpacePaddingTop,
+ modifier = Modifier.fillMaxWidth(0.5f),
+ )
}
}
with(mediaCarouselSection) { KeyguardMediaCarousel() }
@@ -122,7 +143,10 @@
}
@Composable
- private fun SceneScope.SmallClockWithSmartSpace(modifier: Modifier = Modifier) {
+ private fun SceneScope.SmallClockWithSmartSpace(
+ smartSpacePaddingTop: (Resources) -> Int,
+ modifier: Modifier = Modifier,
+ ) {
val burnIn = rememberBurnIn(clockInteractor)
Column(modifier = modifier) {
@@ -137,13 +161,17 @@
SmartSpace(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmartspaceTopChanged,
+ smartSpacePaddingTop = smartSpacePaddingTop,
)
}
}
}
@Composable
- private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) {
+ private fun SceneScope.LargeClockWithSmartSpace(
+ smartSpacePaddingTop: (Resources) -> Int,
+ shouldOffSetClockToOneHalf: Boolean = false,
+ ) {
val burnIn = rememberBurnIn(clockInteractor)
val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
@@ -158,6 +186,7 @@
SmartSpace(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmartspaceTopChanged,
+ smartSpacePaddingTop = smartSpacePaddingTop,
)
}
with(clockSection) {
@@ -180,7 +209,10 @@
}
@Composable
- private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) {
+ private fun SceneScope.WeatherLargeClockWithSmartSpace(
+ smartSpacePaddingTop: (Resources) -> Int,
+ modifier: Modifier = Modifier,
+ ) {
val burnIn = rememberBurnIn(clockInteractor)
val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle()
val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle()
@@ -206,6 +238,7 @@
SmartSpace(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmartspaceTopChanged,
+ smartSpacePaddingTop = smartSpacePaddingTop,
modifier =
Modifier.heightIn(
min = getDimen(context, "enhanced_smartspace_height", density)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 84782fd..0bef05dc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -276,6 +276,7 @@
shouldReserveSpaceForNavBar: Boolean = true,
shouldIncludeHeadsUpSpace: Boolean = true,
shadeMode: ShadeMode,
+ onEmptySpaceClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
@@ -328,8 +329,6 @@
// The height of the scrim visible on screen when it is in its resting (collapsed) state.
val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
- val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
-
// we are not scrolled to the top unless the scrim is at its maximum offset.
LaunchedEffect(viewModel, scrimOffset) {
snapshotFlow { scrimOffset.value >= 0f }
@@ -437,8 +436,8 @@
)
)
}
- .thenIf(isClickable) {
- Modifier.clickable(onClick = { viewModel.onEmptySpaceClicked() })
+ .thenIf(onEmptySpaceClick != null) {
+ Modifier.clickable(onClick = { onEmptySpaceClick?.invoke() })
}
) {
// Creates a cutout in the background scrim in the shape of the notifications scrim.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 7159def..66be7bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -20,15 +20,19 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneContentViewModel
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
@@ -50,9 +54,10 @@
class NotificationsShadeScene
@Inject
constructor(
- sceneViewModel: NotificationsShadeSceneViewModel,
- private val overlayShadeViewModel: OverlayShadeViewModel,
- private val shadeHeaderViewModel: ShadeHeaderViewModel,
+ private val contentViewModelFactory: NotificationsShadeSceneContentViewModel.Factory,
+ private val actionsViewModelFactory: NotificationsShadeSceneActionsViewModel.Factory,
+ private val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
+ private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -64,21 +69,32 @@
override val key = Scenes.NotificationsShade
+ private val actionsViewModel: NotificationsShadeSceneActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- sceneViewModel.destinationScenes
+ actionsViewModel.actions
+
+ override suspend fun activate() {
+ actionsViewModel.activate()
+ }
@Composable
override fun SceneScope.Content(
modifier: Modifier,
) {
+ val viewModel = rememberViewModel { contentViewModelFactory.create() }
+ val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
+
OverlayShade(
modifier = modifier,
- viewModel = overlayShadeViewModel,
+ viewModelFactory = overlayShadeViewModelFactory,
lockscreenContent = lockscreenContent,
) {
Column {
ExpandedShadeHeader(
- viewModel = shadeHeaderViewModel,
+ viewModelFactory = shadeHeaderViewModelFactory,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -94,6 +110,8 @@
shouldFillMaxSize = false,
shouldReserveSpaceForNavBar = false,
shadeMode = ShadeMode.Dual,
+ onEmptySpaceClick =
+ viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier = Modifier.fillMaxWidth(),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index cdcd840..8bba0f4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -81,6 +81,7 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -166,19 +167,23 @@
) {
val cutoutLocation = LocalDisplayCutout.current.location
- val brightnessMirrorShowing by
- viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
+ val brightnessMirrorViewModel = rememberViewModel {
+ viewModel.brightnessMirrorViewModelFactory.create()
+ }
+ val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
targetValue = if (brightnessMirrorShowing) 0f else 1f,
label = "alphaAnimationBrightnessMirrorContentHiding",
)
- viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
- DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
+ notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(contentAlpha)
+ DisposableEffect(Unit) {
+ onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
+ }
BrightnessMirror(
- viewModel = viewModel.brightnessMirrorViewModel,
+ viewModel = brightnessMirrorViewModel,
qsSceneAdapter = viewModel.qsSceneAdapter,
modifier =
Modifier.thenIf(cutoutLocation != CutoutLocation.CENTER) {
@@ -337,7 +342,7 @@
fadeOut(tween(customizingAnimationDuration)),
) {
ExpandedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
+ viewModelFactory = viewModel.shadeHeaderViewModelFactory,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController =
createBatteryMeterViewController,
@@ -347,7 +352,7 @@
}
else ->
CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
+ viewModelFactory = viewModel.shadeHeaderViewModelFactory,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
@@ -417,7 +422,7 @@
)
NotificationStackCutoffGuideline(
stackScrollView = notificationStackScrollView,
- viewModel = viewModel.notifications,
+ viewModel = notificationsPlaceholderViewModel,
modifier =
Modifier.align(Alignment.BottomCenter).navigationBarsPadding().offset {
IntOffset(x = 0, y = screenHeight.roundToInt())
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index f6d1283..eea00c4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -44,12 +44,14 @@
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutEnter
import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutExit
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneContentViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
@@ -66,9 +68,10 @@
class QuickSettingsShadeScene
@Inject
constructor(
- private val viewModel: QuickSettingsShadeSceneViewModel,
+ private val actionsViewModelFactory: QuickSettingsShadeSceneActionsViewModel.Factory,
+ private val contentViewModelFactory: QuickSettingsShadeSceneContentViewModel.Factory,
private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
- private val shadeHeaderViewModel: ShadeHeaderViewModel,
+ private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
@@ -76,21 +79,26 @@
override val key = Scenes.QuickSettingsShade
+ private val actionsViewModel: QuickSettingsShadeSceneActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- viewModel.destinationScenes
+ actionsViewModel.actions
@Composable
override fun SceneScope.Content(
modifier: Modifier,
) {
+ val viewModel = rememberViewModel { contentViewModelFactory.create() }
OverlayShade(
- viewModel = viewModel.overlayShadeViewModel,
+ viewModelFactory = viewModel.overlayShadeViewModelFactory,
lockscreenContent = lockscreenContent,
modifier = modifier,
) {
Column {
ExpandedShadeHeader(
- viewModel = shadeHeaderViewModel,
+ viewModelFactory = shadeHeaderViewModelFactory,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index a9ddf84..b0c3fb31 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -31,7 +31,7 @@
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
+import com.android.systemui.scene.ui.viewmodel.GoneSceneActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
@@ -48,12 +48,18 @@
constructor(
private val notificationStackScrolLView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- private val viewModel: GoneSceneViewModel,
+ private val viewModelFactory: GoneSceneActionsViewModel.Factory,
) : ComposableScene {
override val key = Scenes.Gone
+ private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
+
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- viewModel.destinationScenes
+ actionsViewModel.actions
+
+ override suspend fun activate() {
+ actionsViewModel.activate()
+ }
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index facbcaf..445ffcb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -53,6 +53,7 @@
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
@@ -63,11 +64,12 @@
/** The overlay shade renders a lightweight shade UI container on top of a background scene. */
@Composable
fun SceneScope.OverlayShade(
- viewModel: OverlayShadeViewModel,
+ viewModelFactory: OverlayShadeViewModel.Factory,
lockscreenContent: Lazy<Optional<LockscreenContent>>,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
+ val viewModel = rememberViewModel { viewModelFactory.create() }
val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
Box(modifier) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 1cd48bf..8c53740 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -73,6 +73,7 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -122,12 +123,13 @@
@Composable
fun SceneScope.CollapsedShadeHeader(
- viewModel: ShadeHeaderViewModel,
+ viewModelFactory: ShadeHeaderViewModel.Factory,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val viewModel = rememberViewModel { viewModelFactory.create() }
val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
if (isDisabled) {
return
@@ -279,12 +281,13 @@
@Composable
fun SceneScope.ExpandedShadeHeader(
- viewModel: ShadeHeaderViewModel,
+ viewModelFactory: ShadeHeaderViewModel.Factory,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val viewModel = rememberViewModel { viewModelFactory.create() }
val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle()
if (isDisabled) {
return
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 77b48d3..0e3fcf4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -80,6 +80,7 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.MediaContentPicker
import com.android.systemui.media.controls.ui.composable.shouldElevateMedia
@@ -102,7 +103,8 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -145,7 +147,8 @@
constructor(
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
- private val viewModel: ShadeSceneViewModel,
+ private val actionsViewModelFactory: ShadeSceneActionsViewModel.Factory,
+ private val contentViewModelFactory: ShadeSceneContentViewModel.Factory,
private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -157,12 +160,16 @@
override val key = Scenes.Shade
+ private val actionsViewModel: ShadeSceneActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
override suspend fun activate() {
- viewModel.activate()
+ actionsViewModel.activate()
}
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- viewModel.destinationScenes
+ actionsViewModel.actions
@Composable
override fun SceneScope.Content(
@@ -170,7 +177,7 @@
) =
ShadeScene(
notificationStackScrollView.get(),
- viewModel = viewModel,
+ viewModel = rememberViewModel { contentViewModelFactory.create() },
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
@@ -196,7 +203,7 @@
@Composable
private fun SceneScope.ShadeScene(
notificationStackScrollView: NotificationScrollView,
- viewModel: ShadeSceneViewModel,
+ viewModel: ShadeSceneContentViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -242,7 +249,7 @@
@Composable
private fun SceneScope.SingleShade(
notificationStackScrollView: NotificationScrollView,
- viewModel: ShadeSceneViewModel,
+ viewModel: ShadeSceneContentViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -261,7 +268,7 @@
key = QuickSettings.SharedValues.TilesSquishiness,
canOverflow = false
)
- val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
+ val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
val shouldPunchHoleBehindScrim =
@@ -299,9 +306,9 @@
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
Modifier.fillMaxWidth()
- .thenIf(isClickable) {
+ .thenIf(isEmptySpaceClickable) {
Modifier.clickable(
- onClick = { viewModel.onContentClicked() }
+ onClick = { viewModel.onEmptySpaceClicked() }
)
}
.thenIf(cutoutLocation != CutoutLocation.CENTER) {
@@ -309,7 +316,7 @@
},
) {
CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
+ viewModelFactory = viewModel.shadeHeaderViewModelFactory,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
@@ -361,6 +368,8 @@
maxScrimTop = { maxNotifScrimTop.value },
shadeMode = ShadeMode.Single,
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+ onEmptySpaceClick =
+ viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
)
},
)
@@ -407,7 +416,7 @@
@Composable
private fun SceneScope.SplitShade(
notificationStackScrollView: NotificationScrollView,
- viewModel: ShadeSceneViewModel,
+ viewModel: ShadeSceneContentViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -468,8 +477,10 @@
}
}
- val brightnessMirrorShowing by
- viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
+ val brightnessMirrorViewModel = rememberViewModel {
+ viewModel.brightnessMirrorViewModelFactory.create()
+ }
+ val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
targetValue = if (brightnessMirrorShowing) 0f else 1f,
@@ -481,6 +492,7 @@
onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
}
+ val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
@@ -503,7 +515,7 @@
modifier = Modifier.fillMaxSize(),
) {
CollapsedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
+ viewModelFactory = viewModel.shadeHeaderViewModelFactory,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
@@ -522,7 +534,7 @@
.graphicsLayer { translationX = unfoldTranslationXForStartSide },
) {
BrightnessMirror(
- viewModel = viewModel.brightnessMirrorViewModel,
+ viewModel = brightnessMirrorViewModel,
qsSceneAdapter = viewModel.qsSceneAdapter,
// Need to use the offset measured from the container as the header
// has to be accounted for
@@ -591,6 +603,8 @@
shouldPunchHoleBehindScrim = false,
shouldReserveSpaceForNavBar = false,
shadeMode = ShadeMode.Split,
+ onEmptySpaceClick =
+ viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
modifier =
Modifier.weight(1f)
.fillMaxHeight()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
index c08eb94..981a0ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -20,16 +20,14 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformOutlinedButton
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.BottomBarViewModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -51,16 +49,10 @@
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
- OutlinedButton(
- onClick = viewModel::onSettingsClicked,
- colors =
- ButtonDefaults.outlinedButtonColors(
- contentColor = MaterialTheme.colorScheme.onSurface,
- ),
- ) {
+ PlatformOutlinedButton(onClick = viewModel::onSettingsClicked) {
Text(text = stringResource(R.string.volume_panel_dialog_settings_button))
}
- Button(onClick = viewModel::onDoneClicked) {
+ PlatformButton(onClick = viewModel::onDoneClicked) {
Text(stringResource(R.string.inline_done_button))
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index 0105af3..fe16ef751 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -127,16 +127,7 @@
return coroutineScope
.launch(start = CoroutineStart.ATOMIC) {
try {
- if (currentScene == toScene) {
- animatable.animateTo(targetProgress, transformationSpec.progressSpec)
- } else {
- // If the back gesture is cancelled, the progress is animated back to 0f by
- // the system. But we need this animate call anyways because
- // PredictiveBackHandler doesn't guarantee that it ends at 0f. Since the
- // remaining change in progress is usually very small, the progressSpec is
- // omitted and the default spring spec used instead.
- animatable.animateTo(targetProgress)
- }
+ animatable.animateTo(targetProgress)
} finally {
state.finishTransition(this@PredictiveBackTransition, scene)
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index c414fbe..0eaecb0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -18,8 +18,6 @@
import androidx.activity.BackEventCompat
import androidx.activity.ComponentActivity
-import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.rememberCoroutineScope
@@ -61,23 +59,7 @@
@Test
fun testPredictiveBack() {
- val transitionFrames = 2
- val layoutState =
- rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
- SceneA,
- transitions =
- transitions {
- from(SceneA, to = SceneB) {
- spec =
- tween(
- durationMillis = transitionFrames * 16,
- easing = LinearEasing
- )
- }
- }
- )
- }
+ val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
rule.setContent {
SceneTransitionLayout(layoutState) {
scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -106,27 +88,12 @@
assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
assertThat(layoutState.transitionState).isIdle()
- rule.mainClock.autoAdvance = false
-
// Start again and commit it.
rule.runOnUiThread {
dispatcher.dispatchOnBackStarted(backEvent())
dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
dispatcher.onBackPressed()
}
- rule.mainClock.advanceTimeByFrame()
- rule.waitForIdle()
- val transition2 = assertThat(layoutState.transitionState).isTransition()
- // verify that transition picks up progress from preview
- assertThat(transition2).hasProgress(0.4f, tolerance = 0.0001f)
-
- rule.mainClock.advanceTimeByFrame()
- rule.waitForIdle()
- // verify that transition is half way between preview-end-state (0.4f) and target-state (1f)
- // after one frame
- assertThat(transition2).hasProgress(0.7f, tolerance = 0.0001f)
-
- rule.mainClock.autoAdvance = true
rule.waitForIdle()
assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
assertThat(layoutState.transitionState).isIdle()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 40ea0a0..460461a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -37,7 +37,6 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index fa47a02..4e1f82c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -37,7 +37,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 9c9ee53..b99dec4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -37,7 +37,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class ColorInversionRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
index c0d481c..1378dac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
@@ -35,7 +35,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class OneHandedModeRepositoryImplTest : SysuiTestCase() {
private val testUser1 = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
index ed3b4c0..ce22e28 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -31,7 +31,6 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index 4850085..d244482 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -62,7 +62,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
private KosmosJavaAdapter mKosmos;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 0e98b84..b85e32b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -74,7 +74,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
private KosmosJavaAdapter mKosmos;
@Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 204d4b0..38ea4497 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -79,7 +79,7 @@
// Verifies that a swipe down in the gesture region is captured by the shade touch handler.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeDown_captured() {
val captured = swipe(Direction.DOWN)
Truth.assertThat(captured).isTrue()
@@ -87,7 +87,7 @@
// Verifies that a swipe in the upward direction is not captured.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeUp_notCaptured() {
val captured = swipe(Direction.UP)
@@ -97,7 +97,7 @@
// Verifies that a swipe down forwards captured touches to central surfaces for handling.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -110,7 +110,7 @@
// Verifies that a swipe down forwards captured touches to the shade view for handling.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeDown_communalDisabled_sentToShadeView() {
swipe(Direction.DOWN)
@@ -121,7 +121,7 @@
// Verifies that a swipe down while dreaming forwards captured touches to the shade view for
// handling.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeDown_dreaming_sentToShadeView() {
whenever(mDreamManager.isDreaming).thenReturn(true)
swipe(Direction.DOWN)
@@ -132,7 +132,7 @@
// Verifies that a swipe up is not forwarded to central surfaces.
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeUp_communalEnabled_touchesNotSent() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -146,7 +146,7 @@
// Verifies that a swipe up is not forwarded to the shade view.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testSwipeUp_communalDisabled_touchesNotSent() {
swipe(Direction.UP)
@@ -156,7 +156,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testCancelMotionEvent_popsTouchSession() {
swipe(Direction.DOWN)
val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -165,7 +165,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testFullVerticalSwipe_initiatedWhenAvailable() {
// Indicate touches are available
mTouchHandler.onGlanceableTouchAvailable(true)
@@ -176,7 +176,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
// Indicate touches aren't available
mTouchHandler.onGlanceableTouchAvailable(false)
@@ -187,7 +187,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testFullVerticalSwipe_resetsTouchStateOnUp() {
// Indicate touches are available
mTouchHandler.onGlanceableTouchAvailable(true)
@@ -203,7 +203,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX)
fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
// Indicate touches are available
mTouchHandler.onGlanceableTouchAvailable(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
index 9cfa572..667d364 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
@@ -35,7 +35,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class CameraAutoRotateRepositoryImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index 2911a50..c37b33e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -40,7 +40,6 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var tableLogBuffer: TableLogBuffer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index ad73853..d6712f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -225,7 +225,7 @@
kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
kosmos.fakeKeyguardRepository.setDreaming(true)
kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
- advanceTimeBy(100L)
+ advanceTimeBy(600L)
sceneTransitions.value = hubToBlank
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 1255248..cc945d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -533,6 +533,8 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
+ advanceTimeBy(600L)
+
keyguardRepository.setDreaming(true)
keyguardRepository.setDreamingWithOverlay(true)
advanceTimeBy(60L)
@@ -641,6 +643,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
+ advanceTimeBy(600L)
keyguardRepository.setDreaming(true)
keyguardRepository.setDreamingWithOverlay(true)
advanceTimeBy(60L)
@@ -699,6 +702,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
+ advanceTimeBy(600L)
keyguardRepository.setDreaming(true)
keyguardRepository.setDreamingWithOverlay(true)
advanceTimeBy(60L)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
new file mode 100644
index 0000000..50fdb31
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditWidgetsActivityControllerTest : SysuiTestCase() {
+ @Test
+ fun activityLifecycle_stoppedWhenNotWaitingForResult() {
+ val activity = mock<Activity>()
+ val controller = EditWidgetsActivity.ActivityController(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity).finish()
+ }
+
+ @Test
+ fun activityLifecycle_notStoppedWhenNotWaitingForResult() {
+ val activity = mock<Activity>()
+ val controller = EditWidgetsActivity.ActivityController(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.onWaitingForResult(true)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity, never()).finish()
+ }
+
+ @Test
+ fun activityLifecycle_stoppedAfterResultReturned() {
+ val activity = mock<Activity>()
+ val controller = EditWidgetsActivity.ActivityController(activity)
+
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.onWaitingForResult(true)
+ controller.onWaitingForResult(false)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity).finish()
+ }
+
+ @Test
+ fun activityLifecycle_statePreservedThroughInstanceSave() {
+ val activity = mock<Activity>()
+ val bundle = Bundle(1)
+
+ run {
+ val controller = EditWidgetsActivity.ActivityController(activity)
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ controller.onWaitingForResult(true)
+ callbackCapture.lastValue.onActivitySaveInstanceState(activity, bundle)
+ }
+
+ clearInvocations(activity)
+
+ run {
+ val controller = EditWidgetsActivity.ActivityController(activity)
+ val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+ verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+ callbackCapture.lastValue.onActivityCreated(activity, bundle)
+ callbackCapture.lastValue.onActivityStopped(activity)
+
+ verify(activity, never()).finish()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6412276..3895595 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -62,6 +62,7 @@
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;
@@ -324,4 +325,13 @@
// enabled.
mController.onViewAttached();
}
+
+ @Test
+ public void destroy_cleansUpState() {
+ mController.destroy();
+ verify(mStateController).removeCallback(any());
+ verify(mAmbientStatusBarViewController).destroy();
+ verify(mComplicationHostViewController).destroy();
+ verify(mLowLightTransitionCoordinator).setLowLightEnterListener(ArgumentMatchers.isNull());
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5c09777..7a86e57 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -596,6 +596,9 @@
// are created.
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
verify(mAmbientTouchComponent).getTouchMonitor()
+
+ // Verify DreamOverlayContainerViewController is destroyed.
+ verify(mDreamOverlayContainerViewController).destroy()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 331db52..5dd6c22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -21,15 +21,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.google.common.truth.Truth.assertThat
import java.io.File
-import java.time.Clock
-import java.time.Instant
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
@@ -44,13 +43,13 @@
@RunWith(AndroidJUnit4::class)
class ContextualEducationRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: ContextualEducationRepository
+ private lateinit var underTest: UserContextualEducationRepository
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
private val dsScopeProvider: Provider<CoroutineScope> = Provider {
TestScope(kosmos.testDispatcher).backgroundScope
}
- private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
+
private val testUserId = 1111
// For deleting any test files created after the test
@@ -61,8 +60,7 @@
// Create TestContext here because TemporaryFolder.create() is called in @Before. It is
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
- val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
- underTest = ContextualEducationRepositoryImpl(clock, userRepository)
+ underTest = UserContextualEducationRepository(testContext, dsScopeProvider)
underTest.setUser(testUserId)
}
@@ -70,7 +68,7 @@
fun changeRetrievedValueForNewUser() =
testScope.runTest {
// Update data for old user.
- underTest.incrementSignalCount(BACK)
+ underTest.updateGestureEduModel(BACK) { it.copy(signalCount = 1) }
val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
assertThat(model?.signalCount).isEqualTo(1)
@@ -81,20 +79,19 @@
}
@Test
- fun incrementSignalCount() =
+ fun dataChangedOnUpdate() =
testScope.runTest {
- underTest.incrementSignalCount(BACK)
+ val newModel =
+ GestureEduModel(
+ signalCount = 2,
+ educationShownCount = 1,
+ lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
+ lastEducationTime = kosmos.fakeEduClock.instant(),
+ usageSessionStartTime = kosmos.fakeEduClock.instant(),
+ )
+ underTest.updateGestureEduModel(BACK) { newModel }
val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
- assertThat(model?.signalCount).isEqualTo(1)
- }
-
- @Test
- fun dataAddedOnUpdateShortcutTriggerTime() =
- testScope.runTest {
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
- assertThat(model?.lastShortcutTriggeredTime).isNull()
- underTest.updateShortcutTriggerTime(BACK)
- assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+ assertThat(model).isEqualTo(newModel)
}
/** Test context which allows overriding getFilesDir path */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index ae3302c..6867089 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -19,13 +19,18 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
+import com.android.systemui.education.shared.model.EducationUiType
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -36,8 +41,9 @@
class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository = kosmos.contextualEducationRepository
+ private val contextualEduInteractor = kosmos.contextualEducationInteractor
private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
+ private val eduClock = kosmos.fakeEduClock
@Before
fun setup() {
@@ -47,15 +53,35 @@
@Test
fun newEducationInfoOnMaxSignalCountReached() =
testScope.runTest {
- tryTriggeringEducation(BACK)
+ triggerMaxEducationSignals(BACK)
val model by collectLastValue(underTest.educationTriggered)
assertThat(model?.gestureType).isEqualTo(BACK)
}
@Test
+ fun newEducationToastOn1stEducation() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ triggerMaxEducationSignals(BACK)
+ assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast)
+ }
+
+ @Test
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ fun newEducationNotificationOn2ndEducation() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.educationTriggered)
+ triggerMaxEducationSignals(BACK)
+ // runCurrent() to trigger 1st education
+ runCurrent()
+ triggerMaxEducationSignals(BACK)
+ assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification)
+ }
+
+ @Test
fun noEducationInfoBeforeMaxSignalCountReached() =
testScope.runTest {
- repository.incrementSignalCount(BACK)
+ contextualEduInteractor.incrementSignalCount(BACK)
val model by collectLastValue(underTest.educationTriggered)
assertThat(model).isNull()
}
@@ -64,15 +90,34 @@
fun noEducationInfoWhenShortcutTriggeredPreviously() =
testScope.runTest {
val model by collectLastValue(underTest.educationTriggered)
- repository.updateShortcutTriggerTime(BACK)
- tryTriggeringEducation(BACK)
+ contextualEduInteractor.updateShortcutTriggerTime(BACK)
+ triggerMaxEducationSignals(BACK)
assertThat(model).isNull()
}
- private suspend fun tryTriggeringEducation(gestureType: GestureType) {
+ @Test
+ fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() =
+ testScope.runTest {
+ val model by
+ collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
+ contextualEduInteractor.incrementSignalCount(BACK)
+ eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds))
+ val secondSignalReceivedTime = eduClock.instant()
+ contextualEduInteractor.incrementSignalCount(BACK)
+
+ assertThat(model)
+ .isEqualTo(
+ GestureEduModel(
+ signalCount = 1,
+ usageSessionStartTime = secondSignalReceivedTime
+ )
+ )
+ }
+
+ private suspend fun triggerMaxEducationSignals(gestureType: GestureType) {
// Increment max number of signal to try triggering education
for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
- repository.incrementSignalCount(gestureType)
+ contextualEduInteractor.incrementSignalCount(gestureType)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 273e3cb..fd4ed38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -112,6 +112,26 @@
}
@Test
+ fun onActionDown_whileClicked_startsWait() =
+ testWhileInState(QSLongPressEffect.State.CLICKED) {
+ // GIVEN an action down event occurs
+ longPressEffect.handleActionDown()
+
+ // THEN the effect moves to the TIMEOUT_WAIT state
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+
+ @Test
+ fun onActionDown_whileLongClicked_startsWait() =
+ testWhileInState(QSLongPressEffect.State.LONG_CLICKED) {
+ // GIVEN an action down event occurs
+ longPressEffect.handleActionDown()
+
+ // THEN the effect moves to the TIMEOUT_WAIT state
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+
+ @Test
fun onActionCancel_whileWaiting_goesIdle() =
testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
// GIVEN an action cancel occurs
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 032794c..638c957 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -14,22 +14,6 @@
* limitations under the License.
*/
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor
import android.platform.test.annotations.EnableFlags
@@ -46,17 +30,18 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
-import kotlin.test.Test
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
+import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -79,21 +64,6 @@
@Before
fun setup() {
underTest.start()
-
- kosmos.fakeKeyguardRepository.setDreaming(true)
- kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(true)
-
- // Transition to DOZING and set the power interactor asleep.
- powerInteractor.setAsleepForTest()
- runBlocking {
- transitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DREAMING,
- testScope
- )
- kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
- reset(transitionRepository)
- }
}
@Test
@@ -146,20 +116,27 @@
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionsToLockscreen_whenOccludingActivityEnds() =
testScope.runTest {
+ runCurrent()
+
kosmos.fakeKeyguardRepository.setDreaming(true)
- kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(true)
+ // Transition to DREAMING and set the power interactor awake
+ powerInteractor.setAwakeForTest()
+
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
- testScope,
+ testScope
)
- runCurrent()
+ kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
+ // Get past initial setup
+ advanceTimeBy(600L)
reset(transitionRepository)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
kosmos.fakeKeyguardRepository.setDreaming(false)
- runCurrent()
+ advanceTimeBy(60L)
assertThat(transitionRepository)
.startedTransition(
@@ -171,6 +148,13 @@
@Test
fun testTransitionToAlternateBouncer() =
testScope.runTest {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ reset(transitionRepository)
+
kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index fc827a14..ebefb4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -33,11 +33,15 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchType
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -47,6 +51,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -67,6 +72,7 @@
private val configRepository by lazy { kosmos.fakeConfigurationRepository }
private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
private val shadeRepository by lazy { kosmos.shadeRepository }
+ private val powerInteractor by lazy { kosmos.powerInteractor }
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val transitionState: MutableStateFlow<ObservableTransitionState> =
@@ -350,6 +356,59 @@
}
@Test
+ fun isAbleToDream_falseWhenDozing() =
+ testScope.runTest {
+ val isAbleToDream by collectLastValue(underTest.isAbleToDream)
+
+ repository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE_AOD)
+ )
+
+ assertThat(isAbleToDream).isEqualTo(false)
+ }
+
+ @Test
+ fun isAbleToDream_falseWhenNotDozingAndNotDreaming() =
+ testScope.runTest {
+ val isAbleToDream by collectLastValue(underTest.isAbleToDream)
+
+ repository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(1000L)
+
+ assertThat(isAbleToDream).isEqualTo(false)
+ }
+
+ @Test
+ fun isAbleToDream_trueWhenNotDozingAndIsDreaming_afterDelay() =
+ testScope.runTest {
+ val isAbleToDream by collectLastValue(underTest.isAbleToDream)
+ runCurrent()
+
+ repository.setDreaming(true)
+ repository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // After some delay, still false
+ advanceTimeBy(300L)
+ assertThat(isAbleToDream).isEqualTo(false)
+
+ // After more delay, is true
+ advanceTimeBy(300L)
+ assertThat(isAbleToDream).isEqualTo(true)
+
+ // Also changes back after the minimal debounce
+ repository.setDreaming(false)
+ advanceTimeBy(55L)
+ assertThat(isAbleToDream).isEqualTo(false)
+ }
+
+ @Test
@EnableSceneContainer
fun animationDozingTransitions() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 9762fd8..8c1e8de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -258,7 +258,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- runCurrent()
+ advanceTimeBy(600L)
// GIVEN a prior transition has run to LOCKSCREEN
runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
@@ -287,7 +287,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- runCurrent()
+ advanceTimeBy(600L)
// GIVEN a prior transition has run to LOCKSCREEN
runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
@@ -625,6 +625,9 @@
@DisableSceneContainer
fun dreamingToGoneWithKeyguardNotShowing() =
testScope.runTest {
+ // Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
+ advanceTimeBy(600L)
+
// GIVEN a prior transition has run to DREAMING
keyguardRepository.setDreamingWithOverlay(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
@@ -754,15 +757,35 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
+ fun goneToOccluded() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to GONE
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+ // WHEN an occluding app is running and showDismissibleKeyguard is called
+ keyguardRepository.setKeyguardOccluded(true)
+ keyguardRepository.showDismissibleKeyguard()
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.GONE,
+ to = KeyguardState.OCCLUDED,
+ ownerName =
+ "FromGoneTransitionInteractor" + "(Dismissible keyguard with occlusion)",
+ animatorAssertion = { it.isNotNull() }
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
@DisableSceneContainer
fun goneToDreaming() =
testScope.runTest {
- // GIVEN a device that is not dreaming or dozing
- keyguardRepository.setDreamingWithOverlay(false)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- runCurrent()
+ // Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
+ advanceTimeBy(600L)
// GIVEN a prior transition has run to GONE
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
@@ -1130,6 +1153,9 @@
@DisableSceneContainer
fun primaryBouncerToGlanceableHubWhileDreaming() =
testScope.runTest {
+ // Setup - Move past initial delay with [KeyguardInteractor#isAbleToDream]
+ advanceTimeBy(600L)
+
// GIVEN the device is idle on the glanceable hub
communalSceneInteractor.changeScene(CommunalScenes.Communal)
runCurrent()
@@ -1144,6 +1170,7 @@
// GIVEN that we are dreaming and occluded
keyguardRepository.setDreaming(true)
keyguardRepository.setKeyguardOccluded(true)
+ advanceTimeBy(60L)
// WHEN the primaryBouncer stops showing
bouncerRepository.setPrimaryShow(false)
@@ -2181,12 +2208,14 @@
@DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun glanceableHubToDreaming() =
testScope.runTest {
+ runCurrent()
+
// GIVEN that we are dreaming and not dozing
keyguardRepository.setDreaming(true)
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- runCurrent()
+ advanceTimeBy(600L)
// GIVEN a prior transition has run to GLANCEABLE_HUB
runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.GLANCEABLE_HUB)
@@ -2233,7 +2262,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- advanceTimeBy(100L)
+ advanceTimeBy(600L)
// GIVEN a prior transition has run to GLANCEABLE_HUB
communalSceneInteractor.changeScene(CommunalScenes.Communal)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
index 3f6e229..df8afdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -117,6 +118,24 @@
}
@Test
+ fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor(alpha = { 0.5f })
+ val alpha by collectLastValue(underTest.lockscreenAlpha(viewState))
+
+ keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+ keyguardTransitionRepository.sendTransitionStep(step(0f))
+ assertThat(alpha).isEqualTo(0.5f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(0.5f))
+ assertThat(alpha).isEqualTo(0.75f)
+
+ keyguardTransitionRepository.sendTransitionStep(step(1f))
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun deviceEntryBackgroundViewDisappear() =
testScope.runTest {
val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 3075c54..8236eec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -79,6 +80,7 @@
fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
shadeRepository.setShadeLayoutWide(false)
underTest = lockscreenContentViewModel
+ underTest.activateIn(testScope)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
index bca83f0..b3ea03e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -44,9 +45,7 @@
import com.android.systemui.scene.shared.model.TransitionKeys
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.math.pow
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,7 +62,7 @@
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
@EnableSceneContainer
-class LockscreenSceneViewModelTest : SysuiTestCase() {
+class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
companion object {
private const val parameterCount = 6
@@ -172,6 +171,7 @@
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun destinationScenes() =
testScope.runTest {
+ underTest.activateIn(this)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
if (canSwipeToEnter) {
@@ -192,7 +192,7 @@
},
)
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val destinationScenes by collectLastValue(underTest.actions)
val downDestination =
destinationScenes?.get(
Swipe(
@@ -255,15 +255,10 @@
)
}
- private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
- return LockscreenSceneViewModel(
+ private fun createLockscreenSceneViewModel(): LockscreenSceneActionsViewModel {
+ return LockscreenSceneActionsViewModel(
deviceEntryInteractor = kosmos.deviceEntryInteractor,
communalInteractor = kosmos.communalInteractor,
- touchHandling =
- KeyguardTouchHandlingViewModel(
- interactor = mock(),
- ),
- notifications = kosmos.notificationsPlaceholderViewModel,
shadeInteractor = kosmos.shadeInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
similarity index 78%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
index 16c7090..8f925d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
@@ -31,12 +31,13 @@
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneActionsViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -51,23 +52,24 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
-class NotificationsShadeSceneViewModelTest : SysuiTestCase() {
+class NotificationsShadeSceneActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
- private val underTest by lazy { kosmos.notificationsShadeSceneViewModel }
+ private val underTest by lazy { kosmos.notificationsShadeSceneActionsViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
lockDevice()
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Down)).isNull()
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
.isEqualTo(Scenes.Lockscreen)
}
@@ -75,23 +77,25 @@
@Test
fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
lockDevice()
kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
}
@Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
lockDevice()
unlockDevice()
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Down)).isNull()
assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
}
@@ -99,11 +103,12 @@
fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
testScope.runTest {
kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
lockDevice()
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)).isNull()
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
.isEqualTo(Scenes.Lockscreen)
}
@@ -112,26 +117,28 @@
fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
testScope.runTest {
kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
lockDevice()
unlockDevice()
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)).isNull()
assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
}
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
.isEqualTo(Scenes.Lockscreen)
}
@@ -139,7 +146,7 @@
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
@@ -147,8 +154,9 @@
sceneInteractor // force the lazy; this will kick off StateFlows
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
+ underTest.activateIn(this)
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index c3a5df06..661d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -67,19 +67,25 @@
}
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun resize_updatesSharedPreferences() =
with(kosmos) {
testScope.runTest {
+ qsPreferencesRepository.setLargeTilesSpecs(setOf())
+ runCurrent()
+
val latest by collectLastValue(qsPreferencesRepository.largeTilesSpecs)
val spec = TileSpec.create("large")
// Assert that the tile is added to the large tiles after resizing
- underTest.resize(spec, toIcon = false)
+ underTest.resize(spec)
+ runCurrent()
assertThat(latest).contains(spec)
// Assert that the tile is removed from the large tiles after resizing
- underTest.resize(spec, toIcon = true)
+ underTest.resize(spec)
+ runCurrent()
assertThat(latest).doesNotContain(spec)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
index 45262ca..b2f5765 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -22,6 +22,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
@@ -37,15 +39,15 @@
@Test
fun isMoving_returnsCorrectValue() {
// Asserts no tiles is moving
- TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+ TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
// Start the drag movement
underTest.onStarted(TestEditTiles[0])
// Assert that the correct tile is marked as moving
TestEditTiles.forEach {
- assertThat(underTest.isMoving(it.tileSpec))
- .isEqualTo(TestEditTiles[0].tileSpec == it.tileSpec)
+ assertThat(underTest.isMoving(it.tile.tileSpec))
+ .isEqualTo(TestEditTiles[0].tile.tileSpec == it.tile.tileSpec)
}
}
@@ -55,11 +57,11 @@
underTest.onStarted(TestEditTiles[0])
// Move the tile to the end of the list
- underTest.onMoved(listState.tiles[5].tileSpec)
+ underTest.onMoved(listState.tiles[5].tile.tileSpec)
assertThat(underTest.currentPosition()).isEqualTo(5)
// Move the tile to the middle of the list
- underTest.onMoved(listState.tiles[2].tileSpec)
+ underTest.onMoved(listState.tiles[2].tile.tileSpec)
assertThat(underTest.currentPosition()).isEqualTo(2)
}
@@ -69,13 +71,13 @@
underTest.onStarted(TestEditTiles[0])
// Move the tile to the end of the list
- underTest.onMoved(listState.tiles[5].tileSpec)
+ underTest.onMoved(listState.tiles[5].tile.tileSpec)
// Drop the tile
underTest.onDrop()
// Asserts no tiles is moving
- TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+ TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
}
@Test
@@ -87,19 +89,24 @@
underTest.movedOutOfBounds()
// Asserts the moving tile is not current
- assertThat(listState.tiles.firstOrNull { it.tileSpec == TestEditTiles[0].tileSpec })
+ assertThat(
+ listState.tiles.firstOrNull { it.tile.tileSpec == TestEditTiles[0].tile.tileSpec }
+ )
.isNull()
}
companion object {
- private fun createEditTile(tileSpec: String): EditTileViewModel {
- return EditTileViewModel(
- tileSpec = TileSpec.create(tileSpec),
- icon = Icon.Resource(0, null),
- label = Text.Loaded("unused"),
- appName = null,
- isCurrent = true,
- availableEditActions = emptySet(),
+ private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
+ return SizedTileImpl(
+ EditTileViewModel(
+ tileSpec = TileSpec.create(tileSpec),
+ icon = Icon.Resource(0, null),
+ label = Text.Loaded("unused"),
+ appName = null,
+ isCurrent = true,
+ availableEditActions = emptySet(),
+ ),
+ 1,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index e76d389..a3a6a33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -21,6 +21,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
@@ -35,7 +37,7 @@
@Test
fun movingNonExistentTile_tileAdded() {
val newTile = createEditTile("other_tile", false)
- underTest.move(newTile, TestEditTiles[0].tileSpec)
+ underTest.move(newTile, TestEditTiles[0].tile.tileSpec)
assertThat(underTest.tiles[0]).isEqualTo(newTile)
assertThat(underTest.tiles.subList(1, underTest.tiles.size))
@@ -51,7 +53,7 @@
@Test
fun movingTileToItself_listUnchanged() {
- underTest.move(TestEditTiles[0], TestEditTiles[0].tileSpec)
+ underTest.move(TestEditTiles[0], TestEditTiles[0].tile.tileSpec)
assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
}
@@ -59,7 +61,7 @@
@Test
fun movingTileToSameSection_listUpdates() {
// Move tile at index 0 to index 1. Tile 0 should remain current.
- underTest.move(TestEditTiles[0], TestEditTiles[1].tileSpec)
+ underTest.move(TestEditTiles[0], TestEditTiles[1].tile.tileSpec)
// Assert the tiles 0 and 1 have changed places.
assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
@@ -72,21 +74,27 @@
fun removingTile_listUpdates() {
// Remove tile at index 0
- underTest.remove(TestEditTiles[0].tileSpec)
+ underTest.remove(TestEditTiles[0].tile.tileSpec)
// Assert the tile was removed
assertThat(underTest.tiles).containsExactly(*TestEditTiles.subList(1, 6).toTypedArray())
}
companion object {
- private fun createEditTile(tileSpec: String, isCurrent: Boolean): EditTileViewModel {
- return EditTileViewModel(
- tileSpec = TileSpec.create(tileSpec),
- icon = Icon.Resource(0, null),
- label = Text.Loaded("unused"),
- appName = null,
- isCurrent = isCurrent,
- availableEditActions = emptySet(),
+ private fun createEditTile(
+ tileSpec: String,
+ isCurrent: Boolean
+ ): SizedTile<EditTileViewModel> {
+ return SizedTileImpl(
+ EditTileViewModel(
+ tileSpec = TileSpec.create(tileSpec),
+ icon = Icon.Resource(0, null),
+ label = Text.Loaded("unused"),
+ appName = null,
+ isCurrent = isCurrent,
+ availableEditActions = emptySet(),
+ ),
+ 1,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
index 6df3f8d..0d93686 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PaginatableGridLayoutTest.kt
@@ -19,7 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
@@ -72,10 +72,10 @@
}
companion object {
- fun extraLargeTile() = SizedTile(MockTileViewModel(TileSpec.create("XLarge")), 3)
+ fun extraLargeTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("XLarge")), 3)
- fun largeTile() = SizedTile(MockTileViewModel(TileSpec.create("large")), 2)
+ fun largeTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("large")), 2)
- fun smallTile() = SizedTile(MockTileViewModel(TileSpec.create("small")), 1)
+ fun smallTile() = SizedTileImpl(MockTileViewModel(TileSpec.create("small")), 1)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index cfb84a7..d153e9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
-import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -36,7 +35,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class AutoAddableSettingTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 3ca802e..0363808 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -49,9 +49,8 @@
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -93,10 +92,9 @@
sceneContainerStartable.start()
underTest =
QuickSettingsSceneViewModel(
- brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
+ brightnessMirrorViewModelFactory = kosmos.brightnessMirrorViewModelFactory,
+ shadeHeaderViewModelFactory = kosmos.shadeHeaderViewModelFactory,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = kosmos.notificationsPlaceholderViewModel,
footerActionsViewModelFactory = footerActionsViewModelFactory,
footerActionsController = footerActionsController,
sceneBackInteractor = sceneBackInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
similarity index 77%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
index a7a3a0f..647fdf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
@@ -44,6 +45,7 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,49 +54,54 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
-class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() {
+class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- private val underTest by lazy { kosmos.quickSettingsShadeSceneViewModel }
+ private val underTest by lazy { kosmos.quickSettingsShadeSceneActionsViewModel }
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
@Test
fun upTransitionSceneKey_deviceLocked_lockscreen() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Down)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
}
@Test
fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
@Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
unlockDevice()
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Down)).isNull()
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Down)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
@@ -102,12 +109,12 @@
fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
testScope.runTest {
kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
- assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
}
@@ -115,20 +122,20 @@
fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
testScope.runTest {
kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
lockDevice()
unlockDevice()
- assertThat(destinationScenes?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
- assertThat(destinationScenes?.get(Swipe.Up)).isNull()
+ assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)).isNull()
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -136,14 +143,14 @@
)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
}
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -152,26 +159,26 @@
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
- assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Swipe.Up)?.toScene).isEqualTo(SceneFamilies.Home)
assertThat(homeScene).isEqualTo(Scenes.Gone)
}
@Test
fun backTransitionSceneKey_notEditing_Home() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
- assertThat(destinationScenes?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
+ assertThat(actions?.get(Back)?.toScene).isEqualTo(SceneFamilies.Home)
}
@Test
fun backTransition_editing_noDestination() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val actions by collectLastValue(underTest.actions)
kosmos.editModeViewModel.startEditing()
- assertThat(destinationScenes!!).isNotEmpty()
- assertThat(destinationScenes?.get(Back)).isNull()
+ assertThat(actions!!).isNotEmpty()
+ assertThat(actions?.get(Back)).isNull()
}
private fun TestScope.lockDevice() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/OWNERS
new file mode 100644
index 0000000..e322e38
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/OWNERS
@@ -0,0 +1 @@
+file:/packages/SystemUI/src/com/android/systemui/scene/OWNERS
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 228d61a..66e45ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -48,9 +48,9 @@
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -63,15 +63,15 @@
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeSceneContentViewModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.telecom.telecomManager
import com.google.common.truth.Truth.assertThat
@@ -141,20 +141,16 @@
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
private lateinit var bouncerViewModel: BouncerViewModel
- private val lockscreenSceneViewModel by lazy {
- LockscreenSceneViewModel(
+ private val lockscreenSceneActionsViewModel by lazy {
+ LockscreenSceneActionsViewModel(
deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
- touchHandling =
- KeyguardTouchHandlingViewModel(
- interactor = mock(),
- ),
- notifications = kosmos.notificationsPlaceholderViewModel,
shadeInteractor = kosmos.shadeInteractor,
)
}
- private lateinit var shadeSceneViewModel: ShadeSceneViewModel
+ private lateinit var shadeSceneContentViewModel: ShadeSceneContentViewModel
+ private lateinit var shadeSceneActionsViewModel: ShadeSceneActionsViewModel
private val powerInteractor by lazy { kosmos.powerInteractor }
@@ -193,11 +189,16 @@
bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
bouncerViewModel = kosmos.bouncerViewModel
- shadeSceneViewModel = kosmos.shadeSceneViewModel
+ shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
+ shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
val startable = kosmos.sceneContainerStartable
startable.start()
+ lockscreenSceneActionsViewModel.activateIn(testScope)
+ shadeSceneContentViewModel.activateIn(testScope)
+ shadeSceneActionsViewModel.activateIn(testScope)
+
assertWithMessage("Initial scene key mismatch!")
.that(sceneContainerViewModel.currentScene.value)
.isEqualTo(sceneContainerConfig.initialSceneKey)
@@ -225,8 +226,8 @@
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -245,8 +246,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -256,7 +257,7 @@
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
- val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
+ val actions by collectLastValue(shadeSceneActionsViewModel.actions)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertCurrentScene(Scenes.Lockscreen)
@@ -265,7 +266,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
emulateUserDrivenTransition(
@@ -276,7 +277,7 @@
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
- val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
+ val actions by collectLastValue(shadeSceneActionsViewModel.actions)
val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
@@ -293,7 +294,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
assertThat(homeScene).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
@@ -351,8 +352,8 @@
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
}
@@ -373,8 +374,8 @@
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -391,8 +392,8 @@
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
@@ -411,8 +412,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
- val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
+ val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
+ val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
similarity index 91%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
index a4992e2..b526275 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -42,25 +43,26 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
-class GoneSceneViewModelTest : SysuiTestCase() {
+class GoneSceneActionsViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val shadeRepository by lazy { kosmos.shadeRepository }
- private lateinit var underTest: GoneSceneViewModel
+ private lateinit var underTest: GoneSceneActionsViewModel
@Before
fun setUp() {
underTest =
- GoneSceneViewModel(
+ GoneSceneActionsViewModel(
shadeInteractor = kosmos.shadeInteractor,
)
+ underTest.activateIn(testScope)
}
@Test
fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val destinationScenes by collectLastValue(underTest.actions)
shadeRepository.setShadeLayoutWide(true)
runCurrent()
@@ -71,7 +73,7 @@
@Test
fun downTransitionKey_splitShadeDisabled_isNull() =
testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
+ val destinationScenes by collectLastValue(underTest.actions)
shadeRepository.setShadeLayoutWide(false)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
new file mode 100644
index 0000000..206d3ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneActionsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = FakeSceneActionsViewModel()
+
+ @Test
+ fun actions_emptyBeforeActivation() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+
+ assertThat(underTest.isActive).isFalse()
+ assertThat(actions).isEmpty()
+ }
+
+ @Test
+ fun actions_emptyBeforeFirstValue() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ underTest.activateIn(testScope)
+ runCurrent()
+
+ assertThat(underTest.isActive).isTrue()
+ assertThat(actions).isEmpty()
+ }
+
+ @Test
+ fun actions() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.isActive).isTrue()
+
+ val expected1 =
+ mapOf(
+ Back to UserActionResult(toScene = Scenes.Gone),
+ Swipe(SwipeDirection.Up) to UserActionResult(toScene = Scenes.Shade)
+ )
+ underTest.upstream.value = expected1
+ runCurrent()
+ assertThat(actions).isEqualTo(expected1)
+
+ val expected2 =
+ mapOf(
+ Back to UserActionResult(toScene = Scenes.Lockscreen),
+ Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade)
+ )
+ underTest.upstream.value = expected2
+ runCurrent()
+ assertThat(actions).isEqualTo(expected2)
+ }
+
+ @Test
+ fun actions_emptyAfterCancellation() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val job = Job()
+ underTest.activateIn(testScope, job)
+ runCurrent()
+
+ val expected =
+ mapOf(
+ Back to UserActionResult(toScene = Scenes.Lockscreen),
+ Swipe(SwipeDirection.Down) to UserActionResult(toScene = Scenes.Shade)
+ )
+ underTest.upstream.value = expected
+ runCurrent()
+ assertThat(actions).isEqualTo(expected)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.isActive).isFalse()
+ assertThat(actions).isEmpty()
+ }
+
+ private class FakeSceneActionsViewModel : SceneActionsViewModel() {
+
+ val upstream = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
+
+ override suspend fun hydrateActions(
+ setActions: (Map<UserAction, UserActionResult>) -> Unit,
+ ) {
+ upstream.collectLatest { setActions(it) }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 8a43198..fadb1d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -22,13 +22,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
@@ -43,6 +43,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -50,7 +51,7 @@
private val configurationRepository = kosmos.fakeConfigurationRepository
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val sceneInteractor = kosmos.sceneInteractor
- private val shadeRepository = kosmos.shadeRepository
+ private val shadeTestUtil = kosmos.shadeTestUtil
private val underTest = kosmos.shadeInteractorSceneContainerImpl
@@ -60,7 +61,7 @@
val actual by collectLastValue(underTest.qsExpansion)
// WHEN split shade is enabled and QS is expanded
- overrideResource(R.bool.config_use_split_notification_shade, true)
+ shadeTestUtil.setSplitShade(true)
configurationRepository.onAnyConfigurationChange()
runCurrent()
val transitionState =
@@ -89,7 +90,7 @@
// WHEN split shade is not enabled and QS is expanded
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
runCurrent()
val progress = MutableStateFlow(.3f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
index add33da..6a88664 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
@@ -43,7 +43,7 @@
}
@Test
- fun transitionToOccluded() =
+ fun transitionToOccludedByOCCLUDEDTransition() =
testScope.runTest {
val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
assertThat(isKeyguardOccluded).isFalse()
@@ -62,4 +62,25 @@
)
assertThat(isKeyguardOccluded).isFalse()
}
+
+ @Test
+ fun transitionToOccludedByDREAMINGTransition() =
+ testScope.runTest {
+ val isKeyguardOccluded by collectLastValue(underTest.isKeyguardOccluded)
+ assertThat(isKeyguardOccluded).isFalse()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ assertThat(isKeyguardOccluded).isTrue()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.AOD,
+ testScope,
+ )
+ assertThat(isKeyguardOccluded).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
index 0ffabd8..3f087b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
@@ -37,6 +38,7 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,6 +56,11 @@
private val underTest = kosmos.overlayShadeViewModel
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
@Test
fun backgroundScene_deviceLocked_lockscreen() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 3ded8a3..f6fe667ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -15,6 +15,7 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -52,6 +53,7 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ underTest.activateIn(testScope)
}
@Test
@@ -107,15 +109,15 @@
@Test
fun onSystemIconContainerClicked_unlocked_collapsesShadeToGone() =
- testScope.runTest {
- setDeviceEntered(true)
- setScene(Scenes.Shade)
+ testScope.runTest {
+ setDeviceEntered(true)
+ setScene(Scenes.Shade)
- underTest.onSystemIconContainerClicked()
- runCurrent()
+ underTest.onSystemIconContainerClicked()
+ runCurrent()
- assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
- }
+ assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
+ }
companion object {
private val SUB_1 =
@@ -137,7 +139,7 @@
private fun setScene(key: SceneKey) {
sceneInteractor.changeScene(key, "test")
sceneInteractor.setTransitionState(
- MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
)
testScope.runCurrent()
}
@@ -146,16 +148,16 @@
if (isEntered) {
// Unlock the device marking the device has entered.
kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
+ SuccessFingerprintAuthenticationStatus(0, true)
)
runCurrent()
}
setScene(
- if (isEntered) {
- Scenes.Gone
- } else {
- Scenes.Lockscreen
- }
+ if (isEntered) {
+ Scenes.Gone
+ } else {
+ Scenes.Lockscreen
+ }
)
assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
new file mode 100644
index 0000000..06a02c6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import android.platform.test.annotations.DisableFlags
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+@DisableFlags(DualShade.FLAG_NAME)
+class ShadeSceneActionsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val shadeRepository by lazy { kosmos.shadeRepository }
+ private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
+
+ private val underTest: ShadeSceneActionsViewModel by lazy { kosmos.shadeSceneActionsViewModel }
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun upTransitionSceneKey_deviceLocked_lockScreen() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_deviceUnlocked_gone() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ setDeviceEntered(true)
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun upTransitionSceneKey_keyguardDisabled_gone() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
+ sceneInteractor.changeScene(Scenes.Gone, "reason")
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(SceneFamilies.Home)
+ assertThat(homeScene).isEqualTo(Scenes.Gone)
+ }
+
+ @Test
+ fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ shadeRepository.setShadeLayoutWide(true)
+ runCurrent()
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey)
+ .isEqualTo(ToSplitShade)
+ }
+
+ @Test
+ fun upTransitionKey_splitShadeDisable_isNull() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ shadeRepository.setShadeLayoutWide(false)
+ runCurrent()
+
+ assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
+ }
+
+ @Test
+ fun downTransitionSceneKey_inSplitShade_null() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ kosmos.shadeStartable.start()
+ val actions by collectLastValue(underTest.actions)
+ assertThat(actions?.get(Swipe(SwipeDirection.Down))?.toScene).isNull()
+ }
+
+ @Test
+ fun downTransitionSceneKey_notSplitShade_quickSettings() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ kosmos.shadeStartable.start()
+ val actions by collectLastValue(underTest.actions)
+ assertThat(actions?.get(Swipe(SwipeDirection.Down))?.toScene)
+ .isEqualTo(Scenes.QuickSettings)
+ }
+
+ @Test
+ fun upTransitionSceneKey_customizing_noTransition() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+
+ qsSceneAdapter.setCustomizing(true)
+ assertThat(
+ actions!!.keys.filterIsInstance<Swipe>().filter {
+ it.direction == SwipeDirection.Up
+ }
+ )
+ .isEmpty()
+ }
+
+ private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+ if (isEntered) {
+ // Unlock the device marking the device has entered.
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ }
+ setScene(
+ if (isEntered) {
+ Scenes.Gone
+ } else {
+ Scenes.Lockscreen
+ }
+ )
+ assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+ }
+
+ private fun TestScope.setScene(key: SceneKey) {
+ sceneInteractor.changeScene(key, "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
new file mode 100644
index 0000000..558606f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import android.platform.test.annotations.DisableFlags
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
+import com.google.common.truth.Truth.assertThat
+import java.util.Locale
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+@DisableFlags(DualShade.FLAG_NAME)
+class ShadeSceneContentViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val shadeRepository by lazy { kosmos.shadeRepository }
+ private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
+
+ private val underTest: ShadeSceneContentViewModel by lazy { kosmos.shadeSceneContentViewModel }
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun isEmptySpaceClickable_deviceUnlocked_false() =
+ testScope.runTest {
+ val isEmptySpaceClickable by collectLastValue(underTest.isEmptySpaceClickable)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ setDeviceEntered(true)
+ runCurrent()
+
+ assertThat(isEmptySpaceClickable).isFalse()
+ }
+
+ @Test
+ fun isEmptySpaceClickable_deviceLockedSecurely_true() =
+ testScope.runTest {
+ val isEmptySpaceClickable by collectLastValue(underTest.isEmptySpaceClickable)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ runCurrent()
+
+ assertThat(isEmptySpaceClickable).isTrue()
+ }
+
+ @Test
+ fun onEmptySpaceClicked_deviceLockedSecurely_switchesToLockscreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ runCurrent()
+
+ underTest.onEmptySpaceClicked()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ @Test
+ fun addAndRemoveMedia_mediaVisibilityisUpdated() =
+ testScope.runTest {
+ val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+ val userMedia = MediaData(active = true)
+
+ assertThat(isMediaVisible).isFalse()
+
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+ assertThat(isMediaVisible).isTrue()
+
+ kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+ assertThat(isMediaVisible).isFalse()
+ }
+
+ @Test
+ fun shadeMode() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+
+ shadeRepository.setShadeLayoutWide(true)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+ shadeRepository.setShadeLayoutWide(false)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+ shadeRepository.setShadeLayoutWide(true)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+ }
+
+ @Test
+ fun unfoldTransitionProgress() =
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration()
+ val translations by
+ collectLastValue(
+ combine(
+ underTest.unfoldTranslationX(isOnStartSide = true),
+ underTest.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ Translations(
+ start = start,
+ end = end,
+ )
+ }
+ )
+
+ val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
+ unfoldProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+ }
+
+ private fun prepareConfiguration(): Int {
+ val configuration = context.resources.configuration
+ configuration.setLayoutDirection(Locale.US)
+ kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+ val maxTranslation = 10
+ kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+ R.dimen.notification_side_paddings,
+ maxTranslation
+ )
+ return maxTranslation
+ }
+
+ private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+ if (isEntered) {
+ // Unlock the device marking the device has entered.
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ }
+ setScene(
+ if (isEntered) {
+ Scenes.Gone
+ } else {
+ Scenes.Lockscreen
+ }
+ )
+ assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+ }
+
+ private fun TestScope.setScene(key: SceneKey) {
+ sceneInteractor.changeScene(key, "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ runCurrent()
+ }
+
+ private data class Translations(
+ val start: Float,
+ val end: Float,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
deleted file mode 100644
index 3b2c981..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ /dev/null
@@ -1,370 +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.shade.ui.viewmodel
-
-import android.platform.test.annotations.DisableFlags
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
-import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.media.controls.data.repository.mediaFilterRepository
-import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
-import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.startable.shadeStartable
-import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.testKosmos
-import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
-import com.google.common.truth.Truth.assertThat
-import java.util.Locale
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-@EnableSceneContainer
-@DisableFlags(DualShade.FLAG_NAME)
-class ShadeSceneViewModelTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val shadeRepository by lazy { kosmos.shadeRepository }
- private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
-
- private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
-
- @Before
- fun setUp() {
- underTest.activateIn(testScope)
- }
-
- @Test
- fun upTransitionSceneKey_deviceLocked_lockScreen() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
- )
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun upTransitionSceneKey_deviceUnlocked_gone() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
- )
- setDeviceEntered(true)
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_keyguardDisabled_gone() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
- )
- kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false)
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
- kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
- runCurrent()
- sceneInteractor.changeScene(Scenes.Gone, "reason")
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
- .isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- shadeRepository.setShadeLayoutWide(true)
- runCurrent()
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey)
- .isEqualTo(ToSplitShade)
- }
-
- @Test
- fun upTransitionKey_splitShadeDisable_isNull() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- shadeRepository.setShadeLayoutWide(false)
- runCurrent()
-
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull()
- }
-
- @Test
- fun isClickable_deviceUnlocked_false() =
- testScope.runTest {
- val isClickable by collectLastValue(underTest.isClickable)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
- )
- setDeviceEntered(true)
- runCurrent()
-
- assertThat(isClickable).isFalse()
- }
-
- @Test
- fun isClickable_deviceLockedSecurely_true() =
- testScope.runTest {
- val isClickable by collectLastValue(underTest.isClickable)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
- )
- runCurrent()
-
- assertThat(isClickable).isTrue()
- }
-
- @Test
- fun onContentClicked_deviceLockedSecurely_switchesToLockscreen() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
- )
- runCurrent()
-
- underTest.onContentClicked()
-
- assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- }
-
- @Test
- fun addAndRemoveMedia_mediaVisibilityisUpdated() =
- testScope.runTest {
- val isMediaVisible by collectLastValue(underTest.isMediaVisible)
- val userMedia = MediaData(active = true)
-
- assertThat(isMediaVisible).isFalse()
-
- kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
-
- assertThat(isMediaVisible).isTrue()
-
- kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
-
- assertThat(isMediaVisible).isFalse()
- }
-
- @Test
- fun downTransitionSceneKey_inSplitShade_null() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, true)
- kosmos.shadeStartable.start()
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene).isNull()
- }
-
- @Test
- fun downTransitionSceneKey_notSplitShade_quickSettings() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, false)
- kosmos.shadeStartable.start()
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene)
- .isEqualTo(Scenes.QuickSettings)
- }
-
- @Test
- fun upTransitionSceneKey_customizing_noTransition() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
-
- qsSceneAdapter.setCustomizing(true)
- assertThat(
- destinationScenes!!.keys.filterIsInstance<Swipe>().filter {
- it.direction == SwipeDirection.Up
- }
- )
- .isEmpty()
- }
-
- @Test
- fun shadeMode() =
- testScope.runTest {
- val shadeMode by collectLastValue(underTest.shadeMode)
-
- shadeRepository.setShadeLayoutWide(true)
- assertThat(shadeMode).isEqualTo(ShadeMode.Split)
-
- shadeRepository.setShadeLayoutWide(false)
- assertThat(shadeMode).isEqualTo(ShadeMode.Single)
-
- shadeRepository.setShadeLayoutWide(true)
- assertThat(shadeMode).isEqualTo(ShadeMode.Split)
- }
-
- @Test
- fun unfoldTransitionProgress() =
- testScope.runTest {
- val maxTranslation = prepareConfiguration()
- val translations by
- collectLastValue(
- combine(
- underTest.unfoldTranslationX(isOnStartSide = true),
- underTest.unfoldTranslationX(isOnStartSide = false),
- ) { start, end ->
- Translations(
- start = start,
- end = end,
- )
- }
- )
-
- val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
- unfoldProvider.onTransitionStarted()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
-
- repeat(10) { repetition ->
- val transitionProgress = 0.1f * (repetition + 1)
- unfoldProvider.onTransitionProgress(transitionProgress)
- assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
- assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
- }
-
- unfoldProvider.onTransitionFinishing()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
-
- unfoldProvider.onTransitionFinished()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
- }
-
- private fun prepareConfiguration(): Int {
- val configuration = context.resources.configuration
- configuration.setLayoutDirection(Locale.US)
- kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
- val maxTranslation = 10
- kosmos.fakeConfigurationRepository.setDimensionPixelSize(
- R.dimen.notification_side_paddings,
- maxTranslation
- )
- return maxTranslation
- }
-
- private fun TestScope.setDeviceEntered(isEntered: Boolean) {
- if (isEntered) {
- // Unlock the device marking the device has entered.
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- }
- setScene(
- if (isEntered) {
- Scenes.Gone
- } else {
- Scenes.Lockscreen
- }
- )
- assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
- }
-
- private fun TestScope.setScene(key: SceneKey) {
- sceneInteractor.changeScene(key, "test")
- sceneInteractor.setTransitionState(
- MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
- )
- runCurrent()
- }
-
- private data class Translations(
- val start: Float,
- val end: Float,
- )
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 9fea7a2..733cac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -56,6 +56,7 @@
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -135,11 +136,14 @@
val communalSceneRepository
get() = kosmos.communalSceneRepository
+ val shadeRepository
+ get() = kosmos.fakeShadeRepository
+
lateinit var underTest: SharedNotificationContainerViewModel
@Before
fun setUp() {
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
movementFlow = MutableStateFlow(BurnInModel())
whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
underTest = kosmos.sharedNotificationContainerViewModel
@@ -148,7 +152,7 @@
@Test
fun validateMarginStartInSplitShade() =
testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, true)
+ shadeTestUtil.setSplitShade(true)
overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -161,7 +165,7 @@
@Test
fun validateMarginStart() =
testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -172,10 +176,10 @@
}
@Test
- fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() =
+ fun validatePaddingTopInSplitShade_usesLargeHeaderHelper() =
testScope.runTest {
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- overrideResource(R.bool.config_use_split_notification_shade, true)
+ shadeTestUtil.setSplitShade(true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -191,7 +195,7 @@
fun validatePaddingTopInNonSplitShade_usesLargeScreenHeader() =
testScope.runTest {
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(10)
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -207,7 +211,7 @@
fun validatePaddingTopInNonSplitShade_doesNotUseLargeScreenHeader() =
testScope.runTest {
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(10)
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
overrideResource(R.bool.config_use_large_screen_shade_header, false)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -508,7 +512,7 @@
val bounds by collectLastValue(underTest.bounds)
// When not in split shade
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
runCurrent()
@@ -567,7 +571,7 @@
// When in split shade
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- overrideResource(R.bool.config_use_split_notification_shade, true)
+ shadeTestUtil.setSplitShade(true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -628,7 +632,7 @@
advanceTimeBy(50L)
showLockscreen()
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
assertThat(maxNotifications).isEqualTo(10)
@@ -656,7 +660,7 @@
advanceTimeBy(50L)
showLockscreen()
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
assertThat(maxNotifications).isEqualTo(10)
@@ -690,7 +694,7 @@
// Show lockscreen with shade expanded
showLockscreenWithShadeExpanded()
- overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeTestUtil.setSplitShade(false)
configurationRepository.onAnyConfigurationChange()
// -1 means No Limit
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 2114489..6441405 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -88,7 +88,6 @@
@Mock private PowerManager mPowerManager;
@Mock private WakefulnessLifecycle mWakefullnessLifecycle;
@Mock private CentralSurfaces mCentralSurfaces;
- @Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private ShadeLockscreenInteractor mShadeLockscreenInteractor;
@@ -106,7 +105,7 @@
mHeadsUpManager, mBatteryController, mScrimController,
() -> mBiometricUnlockController, () -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController,
- mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController,
+ mNotificationWakeUpCoordinator, mAuthController,
mShadeLockscreenInteractor, mDozeInteractor);
mDozeServiceHost.initialize(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 32f66c1..11504aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -172,7 +172,7 @@
@Test
fun shouldAskForZenDuration_changesWithSetting() =
testScope.runTest {
- val manualDnd = TestModeBuilder.MANUAL_DND
+ val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
runCurrent()
@@ -201,7 +201,7 @@
@Test
fun activateMode_usesCorrectDuration() =
testScope.runTest {
- val manualDnd = TestModeBuilder.MANUAL_DND
+ val manualDnd = TestModeBuilder.MANUAL_DND_ACTIVE
zenModeRepository.addModes(listOf(manualDnd))
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 62161bf..bcad7e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -69,7 +69,7 @@
.setName("Disabled by other")
.setEnabled(false, /* byUser= */ false)
.build(),
- TestModeBuilder.MANUAL_DND,
+ TestModeBuilder.MANUAL_DND_ACTIVE,
TestModeBuilder()
.setName("Enabled")
.setEnabled(true)
@@ -91,7 +91,7 @@
assertThat(this.enabled).isEqualTo(false)
}
with(tiles?.elementAt(1)!!) {
- assertThat(this.text).isEqualTo("Manual DND")
+ assertThat(this.text).isEqualTo("Do Not Disturb")
assertThat(this.subtext).isEqualTo("On")
assertThat(this.enabled).isEqualTo(true)
}
diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml
index ed1c6c7..badbd88 100644
--- a/packages/SystemUI/res/drawable/ic_bugreport.xml
+++ b/packages/SystemUI/res/drawable/ic_bugreport.xml
@@ -19,14 +19,14 @@
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
- android:tint="?attr/colorControlNormal">
+ android:tint="?android:attr/colorControlNormal">
<path
- android:fillColor="#FF000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
<path
- android:fillColor="#FF000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M10,14h4v2h-4z"/>
<path
- android:fillColor="#FF000000"
+ android:fillColor="#FFFFFFFF"
android:pathData="M10,10h4v2h-4z"/>
</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 8fa975b..e1b8ab4 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,7 +49,7 @@
android:alpha="0.0"
/>
- <!-- LINT.IfChange textColor -->
+ <!-- LINT.IfChange -->
<TextView
android:id="@+id/text"
android:layout_width="0dp"
@@ -78,7 +78,7 @@
android:layout_height="@dimen/chipbar_end_icon_size"
android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
android:src="@drawable/ic_warning"
- android:tint="@color/GM2_red_600"
+ android:tint="@color/GM2_red_800"
android:alpha="0.0"
/>
diff --git a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
index 6180bf5..9e84052 100644
--- a/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
+++ b/packages/SystemUI/res/layout/custom_trace_settings_dialog.xml
@@ -52,7 +52,8 @@
android:layout_weight="1"
android:layout_gravity="fill_vertical"
android:gravity="start"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no" />
<Switch
android:id="@+id/attach_to_bugreport_switch"
@@ -80,7 +81,8 @@
android:layout_weight="1"
android:layout_gravity="fill_vertical"
android:gravity="start"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"/>
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no" />
<Switch
android:id="@+id/winscope_switch"
@@ -108,7 +110,8 @@
android:layout_weight="1"
android:layout_gravity="fill_vertical"
android:gravity="start"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no" />
<Switch
android:id="@+id/trace_debuggable_apps_switch"
@@ -136,7 +139,8 @@
android:layout_weight="1"
android:layout_gravity="fill_vertical"
android:gravity="start"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no" />
<Switch
android:id="@+id/long_traces_switch"
diff --git a/packages/SystemUI/res/raw/trackpad_home_edu.json b/packages/SystemUI/res/raw/trackpad_home_edu.json
new file mode 100644
index 0000000..27db9fd
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_home_edu.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":426,"w":554,"h":564,"nm":"Trackpad-JSON_HomeGesture-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"Home_Dismiss","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":2,"ty":3,"nm":"gesture:scale","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"k":[{"s":[277,197.321,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.13,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.036,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.921,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.779,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.606,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.39,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.122,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.786,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.354,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.78,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.975,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.883,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.652,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,190.304,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.897,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,187.507,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,186.208,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.036,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.998,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.082,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,182.274,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,181.557,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.918,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.344,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.824,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.353,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.924,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.532,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.174,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.843,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.538,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.256,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.995,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.752,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.527,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.319,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.124,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.943,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.776,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.619,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.474,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.339,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.213,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.095,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.985,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.884,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.789,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.62,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.476,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.353,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.209,0],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.039,0],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174,0],"t":380,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.212,0],"t":381,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.896,0],"t":382,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.197,0],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.536,0],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.4,0],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.939,0],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.375,0],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.791,0],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.751,0],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.459,0],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.006,0],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.442,0],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.798,0],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.092,0],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.339,0],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.546,0],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.721,0],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.87,0],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.995,0],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.191,0],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.378,0],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"k":[{"s":[99.914,99.914,100],"t":146,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.848,99.848,100],"t":148,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.751,99.751,100],"t":150,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.685,99.685,100],"t":151,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.605,99.605,100],"t":152,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.507,99.507,100],"t":153,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.387,99.387,100],"t":154,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.239,99.239,100],"t":155,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.056,99.056,100],"t":156,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.829,98.829,100],"t":157,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.542,98.542,100],"t":158,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.174,98.174,100],"t":159,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.686,97.686,100],"t":160,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97,97,100],"t":161,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.071,96.071,100],"t":162,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.025,95.025,100],"t":163,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[93.878,93.878,100],"t":164,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.678,92.678,100],"t":165,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[91.495,91.495,100],"t":166,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[90.39,90.39,100],"t":167,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[89.393,89.393,100],"t":168,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88.508,88.508,100],"t":169,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.729,87.729,100],"t":170,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[87.041,87.041,100],"t":171,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[86.43,86.43,100],"t":172,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.886,85.886,100],"t":173,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[85.397,85.397,100],"t":174,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.956,84.956,100],"t":175,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.555,84.555,100],"t":176,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[84.191,84.191,100],"t":177,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.857,83.857,100],"t":178,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.552,83.552,100],"t":179,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.271,83.271,100],"t":180,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.011,83.011,100],"t":181,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.771,82.771,100],"t":182,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.549,82.549,100],"t":183,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.342,82.342,100],"t":184,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[82.151,82.151,100],"t":185,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.973,81.973,100],"t":186,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.807,81.807,100],"t":187,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.653,81.653,100],"t":188,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.51,81.51,100],"t":189,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.376,81.376,100],"t":190,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.251,81.251,100],"t":191,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.135,81.135,100],"t":192,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.027,81.027,100],"t":193,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.926,80.926,100],"t":194,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.833,80.833,100],"t":195,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.746,80.746,100],"t":196,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.665,80.665,100],"t":197,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.591,80.591,100],"t":198,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.522,80.522,100],"t":199,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.458,80.458,100],"t":200,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.4,80.4,100],"t":201,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.346,80.346,100],"t":202,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.298,80.298,100],"t":203,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.253,80.253,100],"t":204,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.176,80.176,100],"t":206,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.115,80.115,100],"t":208,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.049,80.049,100],"t":211,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80,80,100],"t":380,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.179,80.179,100],"t":381,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[80.757,80.757,100],"t":382,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[81.87,81.87,100],"t":383,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[83.86,83.86,100],"t":384,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[88,88,100],"t":385,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[92.714,92.714,100],"t":386,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[94.789,94.789,100],"t":387,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[95.992,95.992,100],"t":388,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[96.809,96.809,100],"t":389,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.412,97.412,100],"t":390,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[97.878,97.878,100],"t":391,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.249,98.249,100],"t":392,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.553,98.553,100],"t":393,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[98.803,98.803,100],"t":394,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.012,99.012,100],"t":395,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.188,99.188,100],"t":396,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.337,99.337,100],"t":397,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.464,99.464,100],"t":398,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.57,99.57,100],"t":399,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.661,99.661,100],"t":400,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.737,99.737,100],"t":401,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.8,99.8,100],"t":402,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.896,99.896,100],"t":404,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[99.99,99.99,100],"t":408,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":47,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":197,"s":[100]},{"t":203,"s":[0]}]},"r":{"a":0,"k":0},"p":{"k":[{"s":[0,29.984,0],"t":127,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.965,0],"t":128,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.936,0],"t":129,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.894,0],"t":130,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.84,0],"t":131,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.77,0],"t":132,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.682,0],"t":133,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.574,0],"t":134,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.445,0],"t":135,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.294,0],"t":136,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,29.121,0],"t":137,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.925,0],"t":138,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.746,0],"t":139,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.548,0],"t":140,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.33,0],"t":141,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,28.092,0],"t":142,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.832,0],"t":143,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.548,0],"t":144,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,27.239,0],"t":145,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.903,0],"t":146,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.536,0],"t":147,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,26.14,0],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.709,0],"t":149,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,25.241,0],"t":150,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.73,0],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,24.171,0],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,23.563,0],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.898,0],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,22.171,0],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,21.373,0],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,20.496,0],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,19.524,0],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,18.451,0],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,17.263,0],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,15.943,0],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,14.475,0],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,12.841,0],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,11.018,0],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,9.023,0],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,6.87,0],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,4.614,0],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,2.333,0],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0.106,0],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-1.975,0],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-3.877,0],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-5.591,0],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-7.125,0],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-8.492,0],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-9.714,0],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-10.799,0],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-11.771,0],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-12.643,0],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-13.428,0],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.138,0],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-14.777,0],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.355,0],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-15.879,0],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.354,0],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-16.784,0],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.177,0],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.532,0],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-17.854,0],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.146,0],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.409,0],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.645,0],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-18.858,0],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.048,0],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.217,0],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.366,0],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.496,0],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.61,0],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.707,0],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.788,0],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.856,0],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.911,0],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.954,0],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,-19.984,0],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Super Slider","np":3,"mn":"ADBE Slider Control","ix":1,"en":1,"ef":[{"ty":0,"nm":"Slider","mn":"ADBE Slider Control-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":121,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":138,"s":[17.5]},{"t":205,"s":[100]}]}}]}],"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":195,"s":[33,0],"to":[0,0],"ti":[0,0]},{"t":205,"s":[41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"right circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":62,"s":[-41,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":0.56},"o":{"x":0.44,"y":0.44},"t":72,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"i":{"x":0.56,"y":1},"o":{"x":0.44,"y":0},"t":195,"s":[-33,0],"to":[0,0],"ti":[0,0]},{"t":205,"s":[-41,0]}]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"left circle","bm":0,"hd":false},{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":62,"s":[36,36]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":72,"s":[28,28]},{"i":{"x":[0.56,0.56],"y":[1,1]},"o":{"x":[0.44,0.44],"y":[0,0]},"t":195,"s":[28,28]},{"t":205,"s":[36,36]}]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"size","bm":0,"hd":false}],"ip":37,"op":345,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":198,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":201,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":2}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.321],"t":148,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.13],"t":151,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.036],"t":152,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.921],"t":153,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.779],"t":154,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.606],"t":155,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.39],"t":156,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,196.122],"t":157,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.786],"t":158,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,195.354],"t":159,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,194.781],"t":160,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.975],"t":161,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,192.883],"t":162,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,191.652],"t":163,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,190.304],"t":164,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,188.897],"t":165,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,187.507],"t":166,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,186.208],"t":167,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.036],"t":168,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.998],"t":169,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,183.082],"t":170,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,182.274],"t":171,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,181.557],"t":172,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.918],"t":173,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,180.344],"t":174,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.824],"t":175,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,179.353],"t":176,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.924],"t":177,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.532],"t":178,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.174],"t":179,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.843],"t":180,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.538],"t":181,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,177.256],"t":182,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.995],"t":183,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.752],"t":184,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.528],"t":185,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.319],"t":186,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,176.124],"t":187,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.943],"t":188,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.776],"t":189,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.619],"t":190,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.474],"t":191,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.339],"t":192,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.213],"t":193,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.095],"t":194,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,174.985],"t":195,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,175.638],"t":196,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,178.587],"t":197,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,185.09],"t":198,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,193.793],"t":199,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,201.516],"t":200,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,207.702],"t":201,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,212.767],"t":202,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,217.041],"t":203,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,220.728],"t":204,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,223.965],"t":205,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,226.837],"t":206,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,229.392],"t":207,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,231.662],"t":208,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,233.68],"t":209,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,235.467],"t":210,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,237.042],"t":211,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,238.421],"t":212,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.622],"t":213,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.66],"t":214,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.55],"t":215,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.299],"t":216,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.916],"t":217,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.407],"t":218,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.572],"t":220,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,243.29],"t":221,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.866],"t":222,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,242.351],"t":223,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.782],"t":224,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,241.175],"t":225,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.597],"t":226,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.08],"t":227,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.638],"t":228,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.281],"t":229,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.017],"t":230,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.165],"t":238,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.365],"t":240,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.555],"t":242,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.785],"t":245,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.579],"t":383,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,239.389],"t":384,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,240.278],"t":385,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,234.833],"t":386,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,221.896],"t":387,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,215.604],"t":388,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,211.894],"t":389,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,209.347],"t":390,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,207.439],"t":391,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,205.933],"t":392,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,204.711],"t":393,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,203.696],"t":394,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,202.839],"t":395,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,202.106],"t":396,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,201.474],"t":397,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.925],"t":398,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.444],"t":399,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,200.022],"t":400,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.649],"t":401,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.32],"t":402,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,199.03],"t":403,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.776],"t":404,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.552],"t":405,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.355],"t":406,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.183],"t":407,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,198.034],"t":408,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.902],"t":409,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.787],"t":410,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.62],"t":412,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":195,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":225,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":195,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":225,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"matte","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":195,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":225,"s":[0,82.5,0],"h":1},{"i":{"x":0.8,"y":0.15},"o":{"x":0.3,"y":0},"t":380,"s":[0,82.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":386,"s":[0,49.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":416,"s":[0,0,0]}]},"a":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":200,"s":[0,0,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.573,"y":1},"o":{"x":0.236,"y":0},"t":218,"s":[0,-6,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.28,"y":0},"t":232,"s":[0,1.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":252,"s":[0,0,0]}]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.2,0.2],"y":[0,0]},"t":195,"s":[504,315]},{"i":{"x":[0,0],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":225,"s":[30,30]},{"i":{"x":[0.8,0.8],"y":[0.15,0.15]},"o":{"x":[0.3,0.3],"y":[0,0]},"t":380,"s":[30,30]},{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.05,0.05],"y":[0.7,0.7]},"t":386,"s":[219.6,144]},{"t":416,"s":[504,315]}]},"p":{"a":0,"k":[0,0]},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":195,"s":[28]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":225,"s":[30]},{"i":{"x":[0.8],"y":[0.15]},"o":{"x":[0.3],"y":[0]},"t":380,"s":[30]},{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.05],"y":[0.7]},"t":386,"s":[29.2]},{"t":416,"s":[28]}]},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Home_LofiApp","parent":6,"tt":1,"tp":6,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":195,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":225,"s":[10,10,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[10,10,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[46,46,100]},{"t":416,"s":[100,100,100]}]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[503.5,314.5]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,0,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":0,"nm":"Home_LofiLauncher","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":451,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":450,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":451,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Home_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,140,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"Home_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,117.5,0]},"a":{"a":0,"k":[252,275,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[300,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"hotseat - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[168,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":15},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[132,275]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"qsb","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[20.144,20.144],[20.144,-20.144],[0,0],[-20.144,-20.144],[-20.144,20.144],[0,0]],"o":[[-20.144,-20.144],[0,0],[-20.144,20.144],[20.144,20.144],[0,0],[20.144,-20.144]],"v":[[44.892,-44.892],[-28.057,-44.892],[-44.892,-28.057],[-44.892,44.892],[28.057,44.892],[44.892,28.057]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[108,152.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets weather","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[4.782,-2.684],[0,0],[2.63,-0.033],[0,0],[2.807,-4.716],[0,0],[2.263,-1.343],[0,0],[0.066,-5.485],[0,0],[1.292,-2.295],[0,0],[-2.683,-4.784],[0,0],[-0.033,-2.63],[0,0],[-4.716,-2.807],[0,0],[-1.338,-2.263],[0,0],[-5.483,-0.066],[0,0],[-2.296,-1.292],[0,0],[-4.782,2.683],[0,0],[-2.63,0.033],[0,0],[-2.807,4.716],[0,0],[-2.263,1.338],[0,0],[-0.066,5.483],[0,0],[-1.292,2.295],[0,0],[2.683,4.784],[0,0],[0.033,2.631],[0,0],[4.716,2.801],[0,0],[1.338,2.262],[0,0],[5.483,0.068],[0,0],[2.296,1.287]],"o":[[-4.782,-2.684],[0,0],[-2.296,1.287],[0,0],[-5.483,0.068],[0,0],[-1.338,2.262],[0,0],[-4.716,2.801],[0,0],[-0.033,2.631],[0,0],[-2.683,4.784],[0,0],[1.292,2.295],[0,0],[0.066,5.483],[0,0],[2.263,1.338],[0,0],[2.807,4.716],[0,0],[2.63,0.033],[0,0],[4.782,2.683],[0,0],[2.296,-1.292],[0,0],[5.483,-0.066],[0,0],[1.338,-2.263],[0,0],[4.716,-2.807],[0,0],[0.033,-2.63],[0,0],[2.683,-4.784],[0,0],[-1.292,-2.295],[0,0],[-0.066,-5.485],[0,0],[-2.263,-1.343],[0,0],[-2.807,-4.716],[0,0],[-2.63,-0.033],[0,0]],"v":[[7.7,-57.989],[-7.7,-57.989],[-11.019,-56.128],[-18.523,-54.117],[-22.327,-54.07],[-35.668,-46.369],[-37.609,-43.1],[-43.099,-37.605],[-46.372,-35.663],[-54.072,-22.324],[-54.118,-18.522],[-56.132,-11.016],[-57.988,-7.7],[-57.988,7.703],[-56.132,11.019],[-54.118,18.524],[-54.072,22.328],[-46.372,35.669],[-43.099,37.611],[-37.609,43.101],[-35.668,46.373],[-22.327,54.074],[-18.523,54.12],[-11.019,56.133],[-7.7,57.99],[7.7,57.99],[11.019,56.133],[18.523,54.12],[22.327,54.074],[35.668,46.373],[37.609,43.101],[43.099,37.611],[46.372,35.669],[54.072,22.328],[54.118,18.524],[56.132,11.019],[57.988,7.703],[57.988,-7.7],[56.132,-11.016],[54.118,-18.522],[54.072,-22.324],[46.372,-35.663],[43.099,-37.605],[37.609,-43.1],[35.668,-46.369],[22.327,-54.07],[18.523,-54.117],[11.019,-56.128]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[396,104.003]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"widgets clock","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":195,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":204,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":383,"s":[100]},{"t":389,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-29.497,0]},"a":{"a":0,"k":[252,128.003,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[444,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 7","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[348,200.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,128.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252,56.002]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[156,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24]},"p":{"a":0,"k":[0,0]},"nm":"Ellipse Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"apps","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[60,56.004]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Scale Up","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":195,"s":[85,85,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":201,"s":[91,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":231,"s":[100,100,100]},{"i":{"x":[0.8,0.8,0.8],"y":[0.15,0.15,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":380,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.7,0.7,0]},"t":386,"s":[96,96,100]},{"t":416,"s":[90,90,100]}]}},"ao":0,"ef":[{"ty":5,"nm":"Void","np":19,"mn":"Pseudo/250958","ix":1,"en":1,"ef":[{"ty":0,"nm":"Width","mn":"Pseudo/250958-0001","ix":1,"v":{"a":0,"k":100}},{"ty":0,"nm":"Height","mn":"Pseudo/250958-0002","ix":2,"v":{"a":0,"k":100}},{"ty":0,"nm":"Offset X","mn":"Pseudo/250958-0003","ix":3,"v":{"a":0,"k":0}},{"ty":0,"nm":"Offset Y","mn":"Pseudo/250958-0004","ix":4,"v":{"a":0,"k":0}},{"ty":0,"nm":"Roundness","mn":"Pseudo/250958-0005","ix":5,"v":{"a":0,"k":0}},{"ty":6,"nm":"About","mn":"Pseudo/250958-0006","ix":6,"v":0},{"ty":6,"nm":"Plague of null layers.","mn":"Pseudo/250958-0007","ix":7,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0008","ix":8,"v":0},{"ty":6,"nm":"Following projects","mn":"Pseudo/250958-0009","ix":9,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0010","ix":10,"v":0},{"ty":6,"nm":"through time.","mn":"Pseudo/250958-0011","ix":11,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0012","ix":12,"v":0},{"ty":6,"nm":"Be free of the past.","mn":"Pseudo/250958-0013","ix":13,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0014","ix":14,"v":0},{"ty":6,"nm":"Copyright 2023 Battle Axe Inc","mn":"Pseudo/250958-0015","ix":15,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0016","ix":16,"v":0},{"ty":6,"nm":"Void","mn":"Pseudo/250958-0017","ix":17,"v":0}]}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Home_Dismiss","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,282,0]},"a":{"a":0,"k":[277,282,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":554,"h":564,"ip":0,"op":426,"st":-25,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/trackpad_home_success.json b/packages/SystemUI/res/raw/trackpad_home_success.json
new file mode 100644
index 0000000..f14fde5
--- /dev/null
+++ b/packages/SystemUI/res/raw/trackpad_home_success.json
@@ -0,0 +1 @@
+{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_HomeGesture-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadHome_Success_Checkmark","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Check Rotate","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":2,"s":[-16]},{"t":20,"s":[6]}]},"p":{"a":0,"k":[0,0,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[95.049,95.049,100]}},"ao":0,"ip":0,"op":228,"st":-72,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Bounce","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.12],"y":[1]},"o":{"x":[0.44],"y":[0]},"t":12,"s":[0]},{"t":36,"s":[-6]}]},"p":{"a":0,"k":[81,127,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.263,0.263,0.833],"y":[1.126,1.126,1]},"o":{"x":[0.05,0.05,0.05],"y":[0.958,0.958,0]},"t":1,"s":[80,80,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.45,0.45,0.167],"y":[0.325,0.325,0]},"t":20,"s":[105,105,100]},{"t":36,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":-0.289},"p":{"a":0,"k":[14.364,-33.591,0]},"a":{"a":0,"k":[-0.125,0,0]},"s":{"a":0,"k":[104.744,104.744,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-1.401,-0.007],[-10.033,11.235]],"o":[[5.954,7.288],[1.401,0.007],[0,0]],"v":[[-28.591,4.149],[-10.73,26.013],[31.482,-21.255]],"c":false}},"nm":"Path 1","hd":false},{"ty":"tm","s":{"a":0,"k":0},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.22],"y":[1]},"o":{"x":[0.001],"y":[0.149]},"t":10,"s":[29]},{"t":27,"s":[100]}]},"o":{"a":0,"k":0},"m":1,"nm":"Trim Paths 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":11},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":5,"op":44,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[95,95,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":1,"k":[{"i":{"x":[0.275,0.275,0.21],"y":[1.102,1.102,1]},"o":{"x":[0.037,0.037,0.05],"y":[0.476,0.476,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.252,0.252,0.47],"y":[0.159,0.159,0]},"t":16,"s":[120,120,100]},{"t":28,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.1,0.1],"y":[1,1]},"o":{"x":[0.32,0.32],"y":[0.11,0.11]},"t":16,"s":[148,148]},{"t":28,"s":[136,136]}]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":88},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Checkbox - Widget","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"Home_LofiApp","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[339.937,151.75,0]},"a":{"a":0,"k":[339.937,151.75,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[1.021,-1.766],[0,0],[-2.043,0],[0,0],[1.022,1.767]],"o":[[-1.021,-1.766],[0,0],[-1.022,1.767],[0,0],[2.043,0],[0,0]],"v":[[2.297,-7.675],[-2.297,-7.675],[-9.64,5.025],[-7.343,9],[7.343,9],[9.64,5.025]],"c":true}},"nm":"Path 1","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":9},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[481.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Triangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,18]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[457.874,21]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Rectangle","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[292,25]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[334,279]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Text field","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[109,28]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[425.5,208.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[160,56]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400,158.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Sent","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[126,40]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[251,78.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Received","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[334,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[340,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":16},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82,171.125,0]},"a":{"a":0,"k":[82,171.125,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,177.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,165.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,171.125]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 2","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[82.5,140.5,0]},"a":{"a":0,"k":[82,140.938,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,22]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":39.375},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Search","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,31.5]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"header","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,257.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,245.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,251.375]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[132,64]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":12},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Message","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[82,171]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"block","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[64,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[80,96.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[92,8]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[94,84.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Line 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":200},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Avatar","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[34,90.875]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"circle 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[252,157.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"app only","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimaryFixedVariant","cl":"onPrimaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,459,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[200,128]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":18},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705882353,0.258823529412,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Frame 1321317559","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"TrackpadHome_Success_Checkmark","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,198.5,0]},"a":{"a":0,"k":[95,95,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":190,"h":190,"ip":6,"op":50,"st":6,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimaryFixed","cl":"onPrimaryFixed","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":389,"s":[100]},{"t":392,"s":[0]}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.149019607843,0.098039215686,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"matte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"ef":[{"ty":5,"nm":"Global Position","np":4,"mn":"Pseudo/88900","ix":1,"en":1,"ef":[{"ty":10,"nm":"Master Parent","mn":"Pseudo/88900-0001","ix":1,"v":{"a":0,"k":3}},{"ty":3,"nm":"Global Position","mn":"Pseudo/88900-0002","ix":2,"v":{"k":[{"s":[277,197.5],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[277,197.5],"t":49,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]}}]}],"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Home_LofiApp","tt":1,"tp":4,"refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[252,157.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":504,"h":315,"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[277,197.5,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[504,315]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":28},"nm":"Rectangle Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":14},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"op","nm":"Stroke align: Outside","a":{"k":[{"s":[7],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[7],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lj":1,"ml":{"a":0,"k":4},"hd":false},{"ty":"fl","c":{"a":0,"k":[0.925490196078,0.752941176471,0.423529411765,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"frame","bm":0,"hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0350cd7..8cf0fb2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -160,8 +160,8 @@
<color name="GM2_red_300">#F28B82</color>
<color name="GM2_red_500">#EA4335</color>
- <color name="GM2_red_600">#B3261E</color>
<color name="GM2_red_700">#C5221F</color>
+ <color name="GM2_red_800">#B3261E</color>
<color name="GM2_blue_300">#8AB4F8</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8bad971..a5fd5b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -971,8 +971,8 @@
<string name="hearing_devices_presets_error">Couldn\'t update preset</string>
<!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
<string name="hearing_devices_preset_label">Preset</string>
- <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]-->
- <string name="live_caption_title">Live Caption</string>
+ <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
+ <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
@@ -3679,6 +3679,7 @@
-->
<string name="shortcut_helper_key_combinations_or_separator">or</string>
+ <!-- TOUCHPAD TUTORIAL-->
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
<!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
@@ -3687,17 +3688,35 @@
<string name="touchpad_tutorial_action_key_button">Action key</string>
<!-- Label for button finishing touchpad tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_tutorial_done_button">Done</string>
- <!-- Screen title after gesture was done successfully [CHAR LIMIT=NONE] -->
- <string name="touchpad_tutorial_gesture_done">Great job!</string>
+ <!-- BACK GESTURE -->
<!-- Touchpad back gesture action name in tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_action_title">Go back</string>
<!-- Touchpad back gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
<string name="touchpad_back_gesture_guidance">To go back, swipe left or right using three fingers anywhere on the touchpad.\n\nYou can also use the keyboard shortcut
Action + ESC for this.</string>
+ <!-- Screen title after back gesture was done successfully [CHAR LIMIT=NONE] -->
+ <string name="touchpad_back_gesture_success_title">Great job!</string>
<!-- Text shown to the user after they complete back gesture tutorial [CHAR LIMIT=NONE] -->
- <string name="touchpad_back_gesture_finished">You completed the go back gesture.</string>
- <string name="touchpad_back_gesture_animation_content_description">Touchpad showing three fingers moving right and left</string>
- <string name="touchpad_back_gesture_screen_animation_content_description">Device screen showing animation for back gesture</string>
+ <string name="touchpad_back_gesture_success_body">You completed the go back gesture.</string>
+ <!-- HOME GESTURE -->
+ <!-- Touchpad home gesture action name in tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_home_gesture_action_title">Go home</string>
+ <!-- Touchpad home gesture guidance in gestures tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_home_gesture_guidance">To go to your home screen at any time, swipe up with three fingers from the bottom of your screen.</string>
+ <!-- Screen title after home gesture was done successfully [CHAR LIMIT=NONE] -->
+ <string name="touchpad_home_gesture_success_title">Nice!</string>
+ <!-- Text shown to the user after they complete home gesture tutorial [CHAR LIMIT=NONE] -->
+ <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string>
+
+ <!-- KEYBOARD TUTORIAL-->
+ <!-- Action key tutorial title [CHAR LIMIT=NONE] -->
+ <string name="tutorial_action_key_title">Action key</string>
+ <!-- Action key tutorial guidance[CHAR LIMIT=NONE] -->
+ <string name="tutorial_action_key_guidance">To access your apps, press the action key on your keyboard.</string>
+ <!-- Screen title after action key pressed successfully [CHAR LIMIT=NONE] -->
+ <string name="tutorial_action_key_success_title">Congratulations!</string>
+ <!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
+ <string name="tutorial_action_key_success_body">You completed the action key gesture.\n\nAction + / shows all the shortcuts you have available.</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f688d4f..d468f2f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -60,9 +60,7 @@
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -87,7 +85,6 @@
private final StatusBarStateController mStatusBarStateController;
private final ClockRegistry mClockRegistry;
private final KeyguardSliceViewController mKeyguardSliceViewController;
- private final NotificationIconAreaController mNotificationIconAreaController;
private final LockscreenSmartspaceController mSmartspaceController;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
@@ -165,7 +162,6 @@
StatusBarStateController statusBarStateController,
ClockRegistry clockRegistry,
KeyguardSliceViewController keyguardSliceViewController,
- NotificationIconAreaController notificationIconAreaController,
LockscreenSmartspaceController smartspaceController,
NotificationIconContainerAlwaysOnDisplayViewBinder nicViewBinder,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -183,7 +179,6 @@
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
mKeyguardSliceViewController = keyguardSliceViewController;
- mNotificationIconAreaController = notificationIconAreaController;
mSmartspaceController = smartspaceController;
mNicViewBinder = nicViewBinder;
mSecureSettings = secureSettings;
@@ -351,10 +346,8 @@
int getNotificationIconAreaHeight() {
if (MigrateClocksToBlueprint.isEnabled()) {
return 0;
- } else if (NotificationIconContainerRefactor.isEnabled()) {
- return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
} else {
- return mNotificationIconAreaController.getHeight();
+ return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
}
}
@@ -603,16 +596,11 @@
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
com.android.systemui.res.R.id.left_aligned_notification_icon_container);
- if (NotificationIconContainerRefactor.isEnabled()) {
- if (mAodIconsBindHandle != null) {
- mAodIconsBindHandle.dispose();
- }
- if (nic != null) {
- mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
- mAodIconContainer = nic;
- }
- } else {
- mNotificationIconAreaController.setupAodIcons(nic);
+ if (mAodIconsBindHandle != null) {
+ mAodIconsBindHandle.dispose();
+ }
+ if (nic != null) {
+ mAodIconsBindHandle = mNicViewBinder.bindWhileAttached(nic);
mAodIconContainer = nic;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 93c4630..d81a686 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -46,6 +46,7 @@
import androidx.annotation.NonNull;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.Flags;
@@ -193,15 +194,18 @@
private final Context mContext;
private final MagnificationSettingsController.Callback mSettingsControllerCallback;
private final SecureSettings mSecureSettings;
+ private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
SettingsSupplier(Context context,
MagnificationSettingsController.Callback settingsControllerCallback,
DisplayManager displayManager,
- SecureSettings secureSettings) {
+ SecureSettings secureSettings,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
super(displayManager);
mContext = context;
mSettingsControllerCallback = settingsControllerCallback;
mSecureSettings = secureSettings;
+ mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
}
@Override
@@ -213,7 +217,8 @@
windowContext,
new SfVsyncFrameCallbackProvider(),
mSettingsControllerCallback,
- mSecureSettings);
+ mSecureSettings,
+ mViewCaptureAwareWindowManager);
}
}
@@ -227,10 +232,12 @@
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger,
- IWindowManager iWindowManager, AccessibilityManager accessibilityManager) {
+ IWindowManager iWindowManager, AccessibilityManager accessibilityManager,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
this(context, mainHandler.getLooper(), executor, commandQueue,
modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
- displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager);
+ displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager,
+ viewCaptureAwareWindowManager);
}
@VisibleForTesting
@@ -240,7 +247,8 @@
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger,
IWindowManager iWindowManager,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManager accessibilityManager,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
mHandler = new Handler(looper) {
@Override
public void handleMessage(@NonNull Message msg) {
@@ -263,7 +271,8 @@
mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
context, displayManager, mHandler, mExecutor, iWindowManager);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
- mMagnificationSettingsControllerCallback, displayManager, secureSettings);
+ mMagnificationSettingsControllerCallback, displayManager, secureSettings,
+ viewCaptureAwareWindowManager);
mModeSwitchesController.setClickListenerDelegate(
displayId -> mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index d9d9e37..e91bb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -46,6 +46,7 @@
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.ImageView;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.res.R;
@@ -76,6 +77,7 @@
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final ImageView mImageView;
private final Runnable mWindowInsetChangeRunnable;
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@@ -99,17 +101,21 @@
void onClick(int displayId);
}
- MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener) {
- this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener);
+ MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener,
+ viewCaptureAwareWindowManager);
}
@VisibleForTesting
MagnificationModeSwitch(Context context, @NonNull ImageView imageView,
- SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener) {
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
mContext = context;
mConfiguration = new Configuration(context.getResources().getConfiguration());
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mWindowManager = mContext.getSystemService(WindowManager.class);
+ mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
mSfVsyncFrameProvider = sfVsyncFrameProvider;
mClickListener = clickListener;
mParams = createLayoutParams(context);
@@ -276,7 +282,7 @@
mImageView.animate().cancel();
mIsFadeOutAnimating = false;
mImageView.setAlpha(0f);
- mWindowManager.removeView(mImageView);
+ mViewCaptureAwareWindowManager.removeView(mImageView);
mContext.unregisterComponentCallbacks(this);
mIsVisible = false;
}
@@ -310,7 +316,7 @@
mParams.y = mDraggableWindowBounds.bottom;
mToLeftScreenEdge = false;
}
- mWindowManager.addView(mImageView, mParams);
+ mViewCaptureAwareWindowManager.addView(mImageView, mParams);
// Exclude magnification switch button from system gesture area.
setSystemGestureExclusion();
mIsVisible = true;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index caf5517..fc7535a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -26,6 +26,7 @@
import android.util.Range;
import android.view.WindowManager;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -60,8 +61,10 @@
@UiContext Context context,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
@NonNull Callback settingsControllerCallback,
- SecureSettings secureSettings) {
- this(context, sfVsyncFrameProvider, settingsControllerCallback, secureSettings, null);
+ SecureSettings secureSettings,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ this(context, sfVsyncFrameProvider, settingsControllerCallback, secureSettings, null,
+ viewCaptureAwareWindowManager);
}
@VisibleForTesting
@@ -70,7 +73,8 @@
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
@NonNull Callback settingsControllerCallback,
SecureSettings secureSettings,
- WindowMagnificationSettings windowMagnificationSettings) {
+ WindowMagnificationSettings windowMagnificationSettings,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
mContext = context.createWindowContext(
context.getDisplay(),
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
@@ -84,7 +88,7 @@
} else {
mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
mWindowMagnificationSettingsCallback,
- sfVsyncFrameProvider, secureSettings);
+ sfVsyncFrameProvider, secureSettings, viewCaptureAwareWindowManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index 63f9cc2..53827e6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -25,6 +25,7 @@
import android.hardware.display.DisplayManager;
import android.view.Display;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
@@ -47,8 +48,10 @@
private ClickListener mClickListenerDelegate;
@Inject
- public ModeSwitchesController(Context context, DisplayManager displayManager) {
- mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick);
+ public ModeSwitchesController(Context context, DisplayManager displayManager,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick,
+ viewCaptureAwareWindowManager);
}
@VisibleForTesting
@@ -115,6 +118,7 @@
private final Context mContext;
private final ClickListener mClickListener;
+ private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
/**
* Supplies the switch for the given display.
@@ -124,17 +128,20 @@
* @param clickListener The callback that will run when the switch is clicked
*/
SwitchSupplier(Context context, DisplayManager displayManager,
- ClickListener clickListener) {
+ ClickListener clickListener,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
super(displayManager);
mContext = context;
mClickListener = clickListener;
+ mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
}
@Override
protected MagnificationModeSwitch createInstance(Display display) {
final Context uiContext = mContext.createWindowContext(display,
TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
- return new MagnificationModeSwitch(uiContext, mClickListener);
+ return new MagnificationModeSwitch(uiContext, mClickListener,
+ mViewCaptureAwareWindowManager);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 99d966d..9b6501e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -56,6 +56,7 @@
import android.widget.SeekBar;
import android.widget.Switch;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.Flags;
@@ -75,6 +76,7 @@
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final SecureSettings mSecureSettings;
private final Runnable mWindowInsetChangeRunnable;
@@ -135,10 +137,12 @@
@VisibleForTesting
WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
- SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings) {
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
mContext = context;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mWindowManager = mContext.getSystemService(WindowManager.class);
+ mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
mSfVsyncFrameProvider = sfVsyncFrameProvider;
mCallback = callback;
mSecureSettings = secureSettings;
@@ -320,7 +324,7 @@
// Unregister observer before removing view
mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver);
- mWindowManager.removeView(mSettingView);
+ mViewCaptureAwareWindowManager.removeView(mSettingView);
mIsVisible = false;
if (resetPosition) {
mParams.x = 0;
@@ -378,7 +382,7 @@
mParams.y = mDraggableWindowBounds.bottom;
}
- mWindowManager.addView(mSettingView, mParams);
+ mViewCaptureAwareWindowManager.addView(mSettingView, mParams);
mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 083f1db..b6fe0df 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -499,7 +499,8 @@
final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT,
/* flags= */ 0);
if (!resolved.isEmpty()) {
- return new ToolItem(context.getString(R.string.live_caption_title),
+ return new ToolItem(
+ context.getString(R.string.quick_settings_hearing_devices_live_caption_title),
context.getDrawable(R.drawable.ic_volume_odi_captions),
LIVE_CAPTION_INTENT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index b46b8fe..664f3f8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -27,6 +27,7 @@
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.settingslib.Utils;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.res.R;
@@ -105,6 +106,7 @@
private final TextView mNameView;
private final TextView mSummaryView;
private final ImageView mIconView;
+ private final ImageView mGearIcon;
private final View mGearView;
DeviceItemViewHolder(@NonNull View itemView, Context context) {
@@ -114,6 +116,7 @@
mNameView = itemView.requireViewById(R.id.bluetooth_device_name);
mSummaryView = itemView.requireViewById(R.id.bluetooth_device_summary);
mIconView = itemView.requireViewById(R.id.bluetooth_device_icon);
+ mGearIcon = itemView.requireViewById(R.id.gear_icon_image);
mGearView = itemView.requireViewById(R.id.gear_icon);
}
@@ -124,13 +127,31 @@
if (backgroundResId != null) {
mContainer.setBackground(mContext.getDrawable(item.getBackground()));
}
- mNameView.setText(item.getDeviceName());
- mSummaryView.setText(item.getConnectionSummary());
+
+ // tint different color in different state for bad color contrast problem
+ int tintColor = item.isActive() ? Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.materialColorOnPrimaryContainer).getDefaultColor()
+ : Utils.getColorAttr(mContext,
+ com.android.internal.R.attr.materialColorOnSurface).getDefaultColor();
+
Pair<Drawable, String> iconPair = item.getIconWithDescription();
if (iconPair != null) {
- mIconView.setImageDrawable(iconPair.getFirst());
+ Drawable drawable = iconPair.getFirst().mutate();
+ drawable.setTint(tintColor);
+ mIconView.setImageDrawable(drawable);
mIconView.setContentDescription(iconPair.getSecond());
}
+
+ mNameView.setTextAppearance(
+ item.isActive() ? R.style.BluetoothTileDialog_DeviceName_Active
+ : R.style.BluetoothTileDialog_DeviceName);
+ mNameView.setText(item.getDeviceName());
+ mSummaryView.setTextAppearance(
+ item.isActive() ? R.style.BluetoothTileDialog_DeviceSummary_Active
+ : R.style.BluetoothTileDialog_DeviceSummary);
+ mSummaryView.setText(item.getConnectionSummary());
+
+ mGearIcon.getDrawable().mutate().setTint(tintColor);
mGearView.setOnClickListener(view -> callback.onDeviceItemGearClicked(item, view));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index d5790a4..a093f58 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -118,7 +118,8 @@
if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
(abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
distanceY > 0) &&
- if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) touchAvailable
+ else true
} else {
// If the user scrolling favors a vertical direction, begin capturing
// scrolls.
@@ -175,7 +176,7 @@
}
init {
- if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
scope.launch {
communalViewModel.glanceableTouchAvailable.collect {
onGlanceableTouchAvailable(it)
@@ -218,7 +219,7 @@
val normalRegion =
Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
- if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
region.op(bounds, Region.Op.UNION)
exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
}
@@ -265,7 +266,7 @@
when (motionEvent.action) {
MotionEvent.ACTION_CANCEL,
MotionEvent.ACTION_UP -> {
- if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+ if (Flags.hubmodeFullscreenVerticalSwipeFix() && capture == true) {
communalViewModel.onResetTouchState()
}
touchSession?.apply { pop() }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
index 06b41de..9da9a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -61,7 +61,7 @@
private var touchAvailable = false
init {
- if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
scope.launch {
communalViewModel.glanceableTouchAvailable.collect {
onGlanceableTouchAvailable(it)
@@ -107,7 +107,8 @@
capture =
abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
distanceY < 0 &&
- if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) touchAvailable
+ else true
if (capture == true) {
// Send the initial touches over, as the input listener has already
// processed these touches.
@@ -144,7 +145,7 @@
override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
// If fullscreen swipe, use entire space minus exclusion region
- if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
region.op(bounds, Region.Op.UNION)
exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 190bc15..d27e72a 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -122,4 +122,9 @@
* @param session
*/
void onSessionStart(TouchSession session);
+
+ /**
+ * Called when the handler is being torn down.
+ */
+ default void onDestroy() {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index efa55e9..1be6f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -581,6 +581,10 @@
mBoundsFlow.cancel(new CancellationException());
}
+ for (TouchHandler handler : mHandlers) {
+ handler.onDestroy();
+ }
+
mInitialized = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 2d525aa..43ba097 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -699,7 +699,12 @@
}
fun startTransitionToCredentialUI(isError: Boolean) {
- applicationScope.launch {
+ if (!constraintBp()) {
+ applicationScope.launch {
+ viewModel.onSwitchToCredential()
+ legacyCallback?.onUseDeviceCredential()
+ }
+ } else {
viewModel.onSwitchToCredential()
legacyCallback?.onUseDeviceCredential()
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index fbc6470..214420d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -833,7 +833,7 @@
messageJob?.cancel()
messageJob = null
- if (helpMessage.isNotBlank()) {
+ if (helpMessage.isNotBlank() && needsUserConfirmation) {
showHelp(helpMessage)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index 4dafa93..9d82e76 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -297,7 +297,7 @@
val DeviceItem.isMediaDevice: Boolean
get() =
- cachedBluetoothDevice.connectableProfiles.any {
+ cachedBluetoothDevice.uiAccessibleProfiles.any {
it is A2dpProfile ||
it is HearingAidProfile ||
it is LeAudioProfile ||
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 03ef17b..2bcbc9a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,7 +16,10 @@
package com.android.systemui.communal.widgets
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
import android.content.Intent
+import android.content.IntentSender
import android.os.Bundle
import android.os.RemoteException
import android.util.Log
@@ -66,12 +69,78 @@
const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
}
+ /**
+ * [ActivityController] handles closing the activity in the case it is backgrounded without
+ * waiting for an activity result
+ */
+ class ActivityController(activity: Activity) {
+ companion object {
+ private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result"
+ }
+
+ private var waitingForResult: Boolean = false
+
+ init {
+ activity.registerActivityLifecycleCallbacks(
+ object : ActivityLifecycleCallbacks {
+ override fun onActivityCreated(
+ activity: Activity,
+ savedInstanceState: Bundle?
+ ) {
+ waitingForResult =
+ savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
+ ?: false
+ }
+
+ override fun onActivityStarted(activity: Activity) {
+ // Nothing to implement.
+ }
+
+ override fun onActivityResumed(activity: Activity) {
+ // Nothing to implement.
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ // Nothing to implement.
+ }
+
+ override fun onActivityStopped(activity: Activity) {
+ // If we're not backgrounded due to waiting for a resul (either widget
+ // selection
+ // or configuration), finish activity.
+ if (!waitingForResult) {
+ activity.finish()
+ }
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+ outState.putBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT, waitingForResult)
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {
+ // Nothing to implement.
+ }
+ }
+ )
+ }
+
+ /**
+ * Invoked when waiting for an activity result changes, either initiating such wait or
+ * finishing due to the return of a result.
+ */
+ fun onWaitingForResult(waitingForResult: Boolean) {
+ this.waitingForResult = waitingForResult
+ }
+ }
+
private val logger = Logger(logBuffer, "EditWidgetsActivity")
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
private var shouldOpenWidgetPickerOnStart = false
+ private val activityController: ActivityController = ActivityController(this)
+
private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
@@ -154,6 +223,13 @@
// edit mode
communalViewModel.currentScene.first { it == CommunalScenes.Blank }
communalViewModel.setEditModeState(EditModeState.SHOWING)
+
+ // Show the widget picker, if necessary, after the edit activity has animated in.
+ // Waiting until after the activity has appeared avoids transitions issues.
+ if (shouldOpenWidgetPickerOnStart) {
+ onOpenWidgetPicker()
+ shouldOpenWidgetPickerOnStart = false
+ }
}
}
}
@@ -186,7 +262,34 @@
}
}
+ override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
+ activityController.onWaitingForResult(true)
+ super.startActivityForResult(intent, requestCode, options)
+ }
+
+ override fun startIntentSenderForResult(
+ intent: IntentSender,
+ requestCode: Int,
+ fillInIntent: Intent?,
+ flagsMask: Int,
+ flagsValues: Int,
+ extraFlags: Int,
+ options: Bundle?
+ ) {
+ activityController.onWaitingForResult(true)
+ super.startIntentSenderForResult(
+ intent,
+ requestCode,
+ fillInIntent,
+ flagsMask,
+ flagsValues,
+ extraFlags,
+ options
+ )
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ activityController.onWaitingForResult(false)
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
widgetConfigurator.setConfigurationResult(resultCode)
@@ -198,11 +301,6 @@
communalViewModel.setEditActivityShowing(true)
- if (shouldOpenWidgetPickerOnStart) {
- onOpenWidgetPicker()
- shouldOpenWidgetPickerOnStart = false
- }
-
logger.i("Starting the communal widget editor activity")
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 4286646..b0f2c18 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,7 +30,7 @@
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
-import com.android.systemui.inputdevice.oobe.KeyboardTouchpadOobeTutorialCoreStartable
+import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialCoreStartable
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -258,9 +258,9 @@
@Binds
@IntoMap
- @ClassKey(KeyboardTouchpadOobeTutorialCoreStartable::class)
- abstract fun bindOobeSchedulerCoreStartable(
- listener: KeyboardTouchpadOobeTutorialCoreStartable
+ @ClassKey(KeyboardTouchpadTutorialCoreStartable::class)
+ abstract fun bindKeyboardTouchpadTutorialCoreStartable(
+ listener: KeyboardTouchpadTutorialCoreStartable
): CoreStartable
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 609aa39..25b6b14 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -132,7 +132,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
import com.android.systemui.statusbar.phone.LetterboxModule;
-import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -230,7 +229,6 @@
MediaProjectionTaskSwitcherModule.class,
MediaRouterModule.class,
MotionToolModule.class,
- NotificationIconAreaControllerModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 195aa5f..28db3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.model.BiometricMessage
@@ -33,6 +34,7 @@
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.combine
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -64,6 +66,7 @@
activityStarter: ActivityStarter,
powerInteractor: PowerInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ communalSceneInteractor: CommunalSceneInteractor,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
if (KeyguardWmStateRefactor.isEnabled) {
@@ -75,12 +78,20 @@
primaryBouncerInteractor.isShowing,
alternateBouncerInteractor.isVisible,
keyguardInteractor.isDozing,
- ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible, dozing ->
+ communalSceneInteractor.isIdleOnCommunal,
+ ) {
+ occluded,
+ showing,
+ primaryBouncerShowing,
+ alternateBouncerVisible,
+ dozing,
+ isIdleOnCommunal ->
occluded &&
showing &&
!primaryBouncerShowing &&
!alternateBouncerVisible &&
- !dozing
+ !dozing &&
+ !isIdleOnCommunal
}
.distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 69ddb62..40e2f17 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -160,7 +160,10 @@
.stateIn(
bgApplicationScope,
SharingStarted.WhileSubscribed(),
- emptySet(),
+ // This is necessary because there might be multiple displays, and we could
+ // have missed events for those added before this process or flow started.
+ // Note it causes a binder call from the main thread (it's traced).
+ getDisplays().map { display -> display.displayId }.toSet(),
)
} else {
oldEnabledDisplays.map { enabledDisplaysSet ->
@@ -186,8 +189,12 @@
.stateIn(
bgApplicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = setOf(defaultDisplay)
- )
+ // This triggers a single binder call on the UI thread per process. The
+ // alternative would be to use sharedFlows, but they are prohibited due to
+ // performance concerns.
+ // Ultimately, this is a trade-off between a one-time UI thread binder call and
+ // the constant overhead of sharedFlows.
+ initialValue = getDisplays())
} else {
oldEnabledDisplays
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b45ebd8..24ac542 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
@@ -84,51 +85,62 @@
private var mCurrentBlurRadius: Float = 0f
+ private var mLifecycleFlowHandle: DisposableHandle? = null
+
fun init(view: View) {
this.view = view
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- dreamViewModel.dreamOverlayTranslationY.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int -> setElementsTranslationYAtPosition(px, position) },
- POSITION_TOP or POSITION_BOTTOM
- )
+ mLifecycleFlowHandle =
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ dreamViewModel.dreamOverlayTranslationY.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationYAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.dreamOverlayTranslationX.collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int -> setElementsTranslationXAtPosition(px, position) },
- POSITION_TOP or POSITION_BOTTOM
- )
+ launch {
+ dreamViewModel.dreamOverlayTranslationX.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationXAtPosition(px, position)
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.dreamOverlayAlpha.collect { alpha ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = position,
- fadingOut = true,
- )
- },
- POSITION_TOP or POSITION_BOTTOM
- )
+ launch {
+ dreamViewModel.dreamOverlayAlpha.collect { alpha ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
+ )
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
+ }
}
- }
- launch {
- dreamViewModel.transitionEnded.collect { _ ->
- mOverlayStateController.setExitAnimationsRunning(false)
+ launch {
+ dreamViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
}
}
}
- }
+ }
+
+ fun destroy() {
+ mLifecycleFlowHandle?.dispose()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 76c7d23..bf6d266 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -59,6 +59,7 @@
import com.android.systemui.util.ViewController;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.DisposableHandle;
import kotlinx.coroutines.flow.FlowKt;
import java.util.Arrays;
@@ -185,6 +186,8 @@
}
};
+ private DisposableHandle mFlowHandle;
+
@Inject
public DreamOverlayContainerViewController(
DreamOverlayContainerView containerView,
@@ -252,6 +255,17 @@
}
@Override
+ public void destroy() {
+ mStateController.removeCallback(mDreamOverlayStateCallback);
+ mStatusBarViewController.destroy();
+ mComplicationHostViewController.destroy();
+ mDreamOverlayAnimationsController.destroy();
+ mLowLightTransitionCoordinator.setLowLightEnterListener(null);
+
+ super.destroy();
+ }
+
+ @Override
protected void onViewAttached() {
mWakingUpFromSwipe = false;
mJitterStartTimeMillis = System.currentTimeMillis();
@@ -263,7 +277,7 @@
emptyRegion.recycle();
if (dreamHandlesBeingObscured()) {
- collectFlow(
+ mFlowHandle = collectFlow(
mView,
FlowKt.distinctUntilChanged(combineFlows(
mKeyguardTransitionInteractor.isFinishedIn(
@@ -295,6 +309,10 @@
@Override
protected void onViewDetached() {
+ if (mFlowHandle != null) {
+ mFlowHandle.dispose();
+ mFlowHandle = null;
+ }
mHandler.removeCallbacksAndMessages(null);
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 931066d..4b9e5a0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -70,8 +70,12 @@
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -140,6 +144,8 @@
private ComponentName mCurrentBlockedGestureDreamActivityComponent;
+ private final ArrayList<Job> mFlows = new ArrayList<>();
+
/**
* This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
* handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -309,12 +315,12 @@
mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED));
- collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
- mIsCommunalAvailableCallback);
- collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
- mCommunalVisibleConsumer);
- collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
- mBouncerShowingConsumer);
+ mFlows.add(collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
+ mIsCommunalAvailableCallback));
+ mFlows.add(collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
+ mCommunalVisibleConsumer));
+ mFlows.add(collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
+ mBouncerShowingConsumer));
}
@NonNull
@@ -339,6 +345,11 @@
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
+ for (Job job : mFlows) {
+ job.cancel(new CancellationException());
+ }
+ mFlows.clear();
+
mExecutor.execute(() -> {
setLifecycleStateLocked(Lifecycle.State.DESTROYED);
@@ -559,6 +570,7 @@
if (mStarted && mWindow != null) {
try {
+ mWindow.clearContentView();
mWindowManager.removeView(mWindow.getDecorView());
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error removing decor view when resetting overlay", e);
@@ -569,7 +581,10 @@
mStateController.setLowLightActive(false);
mStateController.setEntryAnimationsFinished(false);
- mDreamOverlayContainerViewController = null;
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.destroy();
+ mDreamOverlayContainerViewController = null;
+ }
if (mTouchMonitor != null) {
mTouchMonitor.destroy();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index ee7b6f5..5ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -33,7 +33,11 @@
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -49,6 +53,8 @@
private final ConfigurationInteractor mConfigurationInteractor;
private Boolean mIsEnabled = false;
+ private ArrayList<Job> mFlows = new ArrayList<>();
+
private int mLayoutDirection = LayoutDirection.LTR;
@VisibleForTesting
@@ -70,17 +76,17 @@
mCommunalInteractor = communalInteractor;
mConfigurationInteractor = configurationInteractor;
- collectFlow(
+ mFlows.add(collectFlow(
mLifecycle,
mCommunalInteractor.isCommunalAvailable(),
mIsCommunalAvailableCallback
- );
+ ));
- collectFlow(
+ mFlows.add(collectFlow(
mLifecycle,
mConfigurationInteractor.getLayoutDirection(),
mLayoutDirectionCallback
- );
+ ));
}
@Override
@@ -140,4 +146,13 @@
}
});
}
+
+ @Override
+ public void onDestroy() {
+ for (Job job : mFlows) {
+ job.cancel(new CancellationException());
+ }
+ mFlows.clear();
+ TouchHandler.super.onDestroy();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 532b123..096556f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -18,10 +18,10 @@
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.education.data.repository.ContextualEducationRepository
-import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import com.android.systemui.education.data.repository.UserContextualEducationRepository
import com.android.systemui.education.domain.interactor.ContextualEducationInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
@@ -42,7 +42,7 @@
interface ContextualEducationModule {
@Binds
fun bindContextualEducationRepository(
- impl: ContextualEducationRepositoryImpl
+ impl: UserContextualEducationRepository
): ContextualEducationRepository
@Qualifier annotation class EduDataStoreScope
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index 9f6cb4d..a171f87 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -26,4 +26,6 @@
val signalCount: Int = 0,
val educationShownCount: Int = 0,
val lastShortcutTriggeredTime: Instant? = null,
+ val usageSessionStartTime: Instant? = null,
+ val lastEducationTime: Instant? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
deleted file mode 100644
index 52ccba4..0000000
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.education.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.contextualeducation.GestureType
-import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
-import com.android.systemui.education.data.model.GestureEduModel
-import java.time.Clock
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Encapsulates the functions of ContextualEducationRepository. */
-interface ContextualEducationRepository {
- fun setUser(userId: Int)
-
- fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
-
- suspend fun incrementSignalCount(gestureType: GestureType)
-
- suspend fun updateShortcutTriggerTime(gestureType: GestureType)
-}
-
-/**
- * Provide methods to read and update on field level and allow setting datastore when user is
- * changed
- */
-@SysUISingleton
-class ContextualEducationRepositoryImpl
-@Inject
-constructor(
- @EduClock private val clock: Clock,
- private val userEduRepository: UserContextualEducationRepository
-) : ContextualEducationRepository {
- /** To change data store when user is changed */
- override fun setUser(userId: Int) = userEduRepository.setUser(userId)
-
- override fun readGestureEduModelFlow(gestureType: GestureType) =
- userEduRepository.readGestureEduModelFlow(gestureType)
-
- override suspend fun incrementSignalCount(gestureType: GestureType) {
- userEduRepository.updateGestureEduModel(gestureType) {
- it.copy(signalCount = it.signalCount + 1)
- }
- }
-
- override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
- userEduRepository.updateGestureEduModel(gestureType) {
- it.copy(lastShortcutTriggeredTime = clock.instant())
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 4b37b29..7c3d6338 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -25,9 +25,9 @@
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
+import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.GestureEduModel
import java.time.Instant
@@ -43,10 +43,24 @@
import kotlinx.coroutines.flow.map
/**
- * A contextual education repository to:
- * 1) store education data per user
- * 2) provide methods to read and update data on model-level
- * 3) provide method to enable changing datastore when user is changed
+ * Allows to:
+ * 1) read and update data on model-level
+ * 2) change data store when user is changed
+ */
+interface ContextualEducationRepository {
+ fun setUser(userId: Int)
+
+ fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+ suspend fun updateGestureEduModel(
+ gestureType: GestureType,
+ transform: (GestureEduModel) -> GestureEduModel
+ )
+}
+
+/**
+ * Implementation of [ContextualEducationRepository] that uses [androidx.datastore.preferences.core]
+ * for storage. Data is stored per user.
*/
@SysUISingleton
class UserContextualEducationRepository
@@ -54,11 +68,13 @@
constructor(
@Application private val applicationContext: Context,
@EduDataStoreScope private val dataStoreScopeProvider: Provider<CoroutineScope>
-) {
+) : ContextualEducationRepository {
companion object {
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
+ const val USAGE_SESSION_START_TIME_SUFFIX = "_USAGE_SESSION_START_TIME"
+ const val LAST_EDUCATION_TIME_SUFFIX = "_LAST_EDUCATION_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -70,7 +86,7 @@
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
private val prefData: Flow<Preferences> = datastore.filterNotNull().flatMapLatest { it.data }
- internal fun setUser(userId: Int) {
+ override fun setUser(userId: Int) {
dataStoreScope?.cancel()
val newDsScope = dataStoreScopeProvider.get()
datastore.value =
@@ -85,7 +101,7 @@
dataStoreScope = newDsScope
}
- internal fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
+ override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> =
prefData.map { preferences -> getGestureEduModel(gestureType, preferences) }
private fun getGestureEduModel(
@@ -97,12 +113,20 @@
educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
lastShortcutTriggeredTime =
preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
- Instant.ofEpochMilli(it)
+ Instant.ofEpochSecond(it)
+ },
+ usageSessionStartTime =
+ preferences[getUsageSessionStartTimeKey(gestureType)]?.let {
+ Instant.ofEpochSecond(it)
+ },
+ lastEducationTime =
+ preferences[getLastEducationTimeKey(gestureType)]?.let {
+ Instant.ofEpochSecond(it)
},
)
}
- internal suspend fun updateGestureEduModel(
+ override suspend fun updateGestureEduModel(
gestureType: GestureType,
transform: (GestureEduModel) -> GestureEduModel
) {
@@ -111,11 +135,21 @@
val updatedModel = transform(currentModel)
preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
- updateTimeByInstant(
+ setInstant(
preferences,
updatedModel.lastShortcutTriggeredTime,
getLastShortcutTriggeredTimeKey(gestureType)
)
+ setInstant(
+ preferences,
+ updatedModel.usageSessionStartTime,
+ getUsageSessionStartTimeKey(gestureType)
+ )
+ setInstant(
+ preferences,
+ updatedModel.lastEducationTime,
+ getLastEducationTimeKey(gestureType)
+ )
}
}
@@ -128,13 +162,22 @@
private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
- private fun updateTimeByInstant(
+ private fun getUsageSessionStartTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + USAGE_SESSION_START_TIME_SUFFIX)
+
+ private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
+
+ private fun setInstant(
preferences: MutablePreferences,
instant: Instant?,
key: Preferences.Key<Long>
) {
if (instant != null) {
- preferences[key] = instant.toEpochMilli()
+ // Use epochSecond because an instant is defined as a signed long (64bit number) of
+ // seconds. Using toEpochMilli() on Instant.MIN or Instant.MAX will throw exception
+ // when converting to a long. So we use second instead of milliseconds for storage.
+ preferences[key] = instant.epochSecond
} else {
preferences.remove(key)
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index bee289d..db5c386 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -17,13 +17,15 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.data.repository.ContextualEducationRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import java.time.Clock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -43,6 +45,7 @@
constructor(
@Background private val backgroundScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @EduClock private val clock: Clock,
private val selectedUserInteractor: SelectedUserInteractor,
private val repository: ContextualEducationRepository,
) : CoreStartable {
@@ -64,9 +67,37 @@
.flowOn(backgroundDispatcher)
}
- suspend fun incrementSignalCount(gestureType: GestureType) =
- repository.incrementSignalCount(gestureType)
+ suspend fun incrementSignalCount(gestureType: GestureType) {
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(
+ signalCount = it.signalCount + 1,
+ usageSessionStartTime =
+ if (it.signalCount == 0) clock.instant() else it.usageSessionStartTime
+ )
+ }
+ }
- suspend fun updateShortcutTriggerTime(gestureType: GestureType) =
- repository.updateShortcutTriggerTime(gestureType)
+ suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(lastShortcutTriggeredTime = clock.instant())
+ }
+ }
+
+ suspend fun updateOnEduTriggered(gestureType: GestureType) {
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(
+ // Reset signal counter and usageSessionStartTime after edu triggered
+ signalCount = 0,
+ lastEducationTime = clock.instant(),
+ educationShownCount = it.educationShownCount + 1,
+ usageSessionStartTime = null
+ )
+ }
+ }
+
+ suspend fun startNewUsageSession(gestureType: GestureType) {
+ repository.updateGestureEduModel(gestureType) {
+ it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 9016c73..3a3fb8c 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -17,17 +17,19 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.CoreStartable
+import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
+import java.time.Clock
import javax.inject.Inject
+import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
/** Allow listening to new contextual education triggered */
@@ -36,11 +38,13 @@
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- private val contextualEducationInteractor: ContextualEducationInteractor
+ private val contextualEducationInteractor: ContextualEducationInteractor,
+ @EduClock private val clock: Clock,
) : CoreStartable {
companion object {
const val MAX_SIGNAL_COUNT: Int = 2
+ val usageSessionDuration = 72.hours
}
private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
@@ -48,25 +52,30 @@
override fun start() {
backgroundScope.launch {
- contextualEducationInteractor.backGestureModelFlow
- .mapNotNull { getEduType(it) }
- .collect { _educationTriggered.value = EducationInfo(BACK, it) }
- }
- }
-
- private fun getEduType(model: GestureEduModel): EducationUiType? {
- if (isEducationNeeded(model)) {
- return EducationUiType.Toast
- } else {
- return null
+ contextualEducationInteractor.backGestureModelFlow.collect {
+ if (isUsageSessionExpired(it)) {
+ contextualEducationInteractor.startNewUsageSession(BACK)
+ } else if (isEducationNeeded(it)) {
+ _educationTriggered.value = EducationInfo(BACK, getEduType(it))
+ contextualEducationInteractor.updateOnEduTriggered(BACK)
+ }
+ }
}
}
private fun isEducationNeeded(model: GestureEduModel): Boolean {
// Todo: b/354884305 - add complete education logic to show education in correct scenarios
- val shortcutWasTriggered = model.lastShortcutTriggeredTime == null
+ val noShortcutTriggered = model.lastShortcutTriggeredTime == null
val signalCountReached = model.signalCount >= MAX_SIGNAL_COUNT
-
- return shortcutWasTriggered && signalCountReached
+ return noShortcutTriggered && signalCountReached
}
+
+ private fun isUsageSessionExpired(model: GestureEduModel): Boolean {
+ return model.usageSessionStartTime
+ ?.plusSeconds(usageSessionDuration.inWholeSeconds)
+ ?.isBefore(clock.instant()) ?: false
+ }
+
+ private fun getEduType(model: GestureEduModel) =
+ if (model.educationShownCount > 0) EducationUiType.Notification else EducationUiType.Toast
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 59de203..cd0b3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -34,19 +34,14 @@
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
-import com.android.systemui.qs.flags.NewQsUI
-import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
-import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
@@ -61,11 +56,9 @@
modesUi dependsOn modesApi
// Internal notification frontend dependencies
- NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
- FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
- NotificationMinimalismPrototype.token dependsOn NotificationsHeadsUpRefactor.token
+ NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
NotificationsHeadsUpRefactor.token dependsOn NotificationThrottleHun.token
// SceneContainer dependencies
@@ -81,9 +74,6 @@
// DualShade dependencies
DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag()
- // QS Fragment using Compose dependencies
- QSComposeFragment.token dependsOn NewQsUI.token
-
// Status bar chip dependencies
statusBarCallChipNotificationIconToken dependsOn statusBarUseReposForCallChipToken
statusBarCallChipNotificationIconToken dependsOn statusBarScreenSharingChipsToken
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 4652b2a..e50c05c 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -107,7 +107,11 @@
fun handleActionDown() {
logEvent(qsTile?.tileSpec, state, "action down received")
when (state) {
- State.IDLE -> {
+ State.IDLE,
+ // ACTION_DOWN typically only happens in State.IDLE but including CLICKED and
+ // LONG_CLICKED just to be safe`b
+ State.CLICKED,
+ State.LONG_CLICKED -> {
setState(State.TIMEOUT_WAIT)
}
State.RUNNING_BACKWARDS_FROM_UP,
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
deleted file mode 100644
index b014c08..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/domain/interactor/OobeSchedulerInteractor.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.inputdevice.oobe.domain.interactor
-
-import android.content.Context
-import android.content.Intent
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.inputdevice.oobe.data.model.DeviceSchedulerInfo
-import com.android.systemui.inputdevice.oobe.data.model.OobeSchedulerInfo
-import com.android.systemui.keyboard.data.repository.KeyboardRepository
-import com.android.systemui.touchpad.data.repository.TouchpadRepository
-import java.time.Duration
-import java.time.Instant
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-
-/**
- * When the first time a keyboard or touchpad id connected, wait for [LAUNCH_DELAY], then launch the
- * tutorial as soon as there's a connected device
- */
-@SysUISingleton
-class OobeSchedulerInteractor
-@Inject
-constructor(
- @Application private val context: Context,
- @Application private val applicationScope: CoroutineScope,
- private val keyboardRepository: KeyboardRepository,
- private val touchpadRepository: TouchpadRepository
-) {
- private val info = OobeSchedulerInfo()
-
- fun start() {
- if (!info.keyboard.isLaunched) {
- applicationScope.launch {
- schedule(keyboardRepository.isAnyKeyboardConnected, info.keyboard)
- }
- }
- if (!info.touchpad.isLaunched) {
- applicationScope.launch {
- schedule(touchpadRepository.isAnyTouchpadConnected, info.touchpad)
- }
- }
- }
-
- private suspend fun schedule(isAnyDeviceConnected: Flow<Boolean>, info: DeviceSchedulerInfo) {
- if (!info.wasEverConnected) {
- waitForDeviceConnection(isAnyDeviceConnected)
- info.connectionTime = Instant.now().toEpochMilli()
- }
- delay(remainingTimeMillis(info.connectionTime!!))
- waitForDeviceConnection(isAnyDeviceConnected)
- info.isLaunched = true
- launchOobe()
- }
-
- private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
- return isAnyDeviceConnected.filter { it }.first()
- }
-
- private fun launchOobe() {
- val intent = Intent(TUTORIAL_ACTION)
- intent.addCategory(Intent.CATEGORY_DEFAULT)
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(intent)
- }
-
- private fun remainingTimeMillis(start: Long): Long {
- val elapsed = Instant.now().toEpochMilli() - start
- return LAUNCH_DELAY - elapsed
- }
-
- companion object {
- const val TAG = "OobeSchedulerInteractor"
- const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
- private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index 701d3da..e8e1dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadOobeTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -14,23 +14,24 @@
* limitations under the License.
*/
-package com.android.systemui.inputdevice.oobe
+package com.android.systemui.inputdevice.tutorial
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.inputdevice.oobe.domain.interactor.OobeSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
import dagger.Lazy
import javax.inject.Inject
-/** A [CoreStartable] to launch a scheduler for keyboard and touchpad OOBE education */
+/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
@SysUISingleton
-class KeyboardTouchpadOobeTutorialCoreStartable
+class KeyboardTouchpadTutorialCoreStartable
@Inject
-constructor(private val oobeSchedulerInteractor: Lazy<OobeSchedulerInteractor>) : CoreStartable {
+constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
+ CoreStartable {
override fun start() {
if (newTouchpadGesturesTutorial()) {
- oobeSchedulerInteractor.get().start()
+ tutorialSchedulerInteractor.get().start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
index e5aedc0..cfe64e2 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.inputdevice.oobe.data.model
+package com.android.systemui.inputdevice.tutorial.data.model
-data class OobeSchedulerInfo(
+data class TutorialSchedulerInfo(
val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
)
-data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectionTime: Long? = null) {
+data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) {
val wasEverConnected: Boolean
- get() = connectionTime != null
+ get() = connectTime != null
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
new file mode 100644
index 0000000..31ff018
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputdevice.tutorial.data.repository
+
+import android.content.Context
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.longPreferencesKey
+import androidx.datastore.preferences.preferencesDataStore
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
+import com.android.systemui.inputdevice.tutorial.data.model.TutorialSchedulerInfo
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class TutorialSchedulerRepository
+@Inject
+constructor(@Application private val applicationContext: Context) {
+
+ private val Context.dataStore: DataStore<Preferences> by
+ preferencesDataStore(name = DATASTORE_NAME)
+
+ suspend fun loadData(): TutorialSchedulerInfo {
+ return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first()
+ }
+
+ suspend fun updateConnectTime(device: DeviceType, time: Long) {
+ applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time }
+ }
+
+ suspend fun updateLaunch(device: DeviceType) {
+ applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true }
+ }
+
+ private fun getSchedulerInfo(pref: Preferences): TutorialSchedulerInfo {
+ return TutorialSchedulerInfo(
+ keyboard = getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD),
+ touchpad = getDeviceSchedulerInfo(pref, DeviceType.TOUCHPAD)
+ )
+ }
+
+ private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo {
+ val isLaunched = pref[getLaunchedKey(device)] ?: false
+ val connectionTime = pref[getConnectKey(device)] ?: null
+ return DeviceSchedulerInfo(isLaunched, connectionTime)
+ }
+
+ private fun getLaunchedKey(device: DeviceType) =
+ booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX)
+
+ private fun getConnectKey(device: DeviceType) =
+ longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
+
+ companion object {
+ const val DATASTORE_NAME = "TutorialScheduler"
+ const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED"
+ const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME"
+ }
+}
+
+enum class DeviceType {
+ KEYBOARD,
+ TOUCHPAD
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
new file mode 100644
index 0000000..05e1044
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputdevice.tutorial.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import java.time.Duration
+import java.time.Instant
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], then launch the
+ * tutorial as soon as there's a connected device
+ */
+@SysUISingleton
+class TutorialSchedulerInteractor
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Application private val applicationScope: CoroutineScope,
+ private val keyboardRepository: KeyboardRepository,
+ private val touchpadRepository: TouchpadRepository,
+ private val tutorialSchedulerRepository: TutorialSchedulerRepository
+) {
+ fun start() {
+ applicationScope.launch {
+ val info = tutorialSchedulerRepository.loadData()
+ if (!info.keyboard.isLaunched) {
+ applicationScope.launch {
+ schedule(
+ keyboardRepository.isAnyKeyboardConnected,
+ info.keyboard,
+ DeviceType.KEYBOARD
+ )
+ }
+ }
+ if (!info.touchpad.isLaunched) {
+ applicationScope.launch {
+ schedule(
+ touchpadRepository.isAnyTouchpadConnected,
+ info.touchpad,
+ DeviceType.TOUCHPAD
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun schedule(
+ isAnyDeviceConnected: Flow<Boolean>,
+ info: DeviceSchedulerInfo,
+ deviceType: DeviceType
+ ) {
+ if (!info.wasEverConnected) {
+ waitForDeviceConnection(isAnyDeviceConnected)
+ info.connectTime = Instant.now().toEpochMilli()
+ tutorialSchedulerRepository.updateConnectTime(deviceType, info.connectTime!!)
+ }
+ delay(remainingTimeMillis(info.connectTime!!))
+ waitForDeviceConnection(isAnyDeviceConnected)
+ info.isLaunched = true
+ tutorialSchedulerRepository.updateLaunch(deviceType)
+ launchTutorial()
+ }
+
+ private suspend fun waitForDeviceConnection(isAnyDeviceConnected: Flow<Boolean>): Boolean {
+ return isAnyDeviceConnected.filter { it }.first()
+ }
+
+ private fun launchTutorial() {
+ val intent = Intent(TUTORIAL_ACTION)
+ intent.addCategory(Intent.CATEGORY_DEFAULT)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+
+ private fun remainingTimeMillis(start: Long): Long {
+ val elapsed = Instant.now().toEpochMilli() - start
+ return LAUNCH_DELAY - elapsed
+ }
+
+ companion object {
+ const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL"
+ private val LAUNCH_DELAY = Duration.ofHours(72).toMillis()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index 90867edd..da671e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyboard
-import android.hardware.input.InputSettings
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
@@ -40,12 +39,10 @@
private val featureFlags: FeatureFlags,
) : CoreStartable {
override fun start() {
+ stickyKeysIndicatorCoordinator.get().startListening()
if (featureFlags.isEnabled(LegacyFlag.KEYBOARD_BACKLIGHT_INDICATOR)) {
keyboardBacklightDialogCoordinator.get().startListening()
}
- if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
- stickyKeysIndicatorCoordinator.get().startListening()
- }
if (Flags.keyboardDockingIndicator()) {
keyboardDockingIndicationViewBinder.get().startListening()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 9f33113..871d046 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -645,6 +645,9 @@
public void showDismissibleKeyguard() {
trace("showDismissibleKeyguard");
checkPermission();
+ if (mFoldGracePeriodProvider.get().isEnabled()) {
+ mKeyguardInteractor.showDismissibleKeyguard();
+ }
mKeyguardViewMediator.showDismissibleKeyguard();
if (SceneContainerFlag.isEnabled() && mFoldGracePeriodProvider.get().isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 80cf4c5..ba533ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -106,7 +106,7 @@
private val falsingManager: FalsingManager,
private val keyguardClockViewModel: KeyguardClockViewModel,
private val smartspaceViewModel: KeyguardSmartspaceViewModel,
- private val lockscreenContentViewModel: LockscreenContentViewModel,
+ private val lockscreenContentViewModelFactory: LockscreenContentViewModel.Factory,
private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
private val clockInteractor: KeyguardClockInteractor,
private val keyguardViewMediator: KeyguardViewMediator,
@@ -143,7 +143,7 @@
val composeView =
createLockscreen(
context = context,
- viewModel = lockscreenContentViewModel,
+ viewModelFactory = lockscreenContentViewModelFactory,
blueprints = lockscreenSceneBlueprintsLazy.get(),
)
composeView.id = View.generateViewId()
@@ -224,7 +224,7 @@
private fun createLockscreen(
context: Context,
- viewModel: LockscreenContentViewModel,
+ viewModelFactory: LockscreenContentViewModel.Factory,
blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
): View {
val sceneBlueprints =
@@ -239,7 +239,7 @@
scene(currentScene) {
with(
LockscreenContent(
- viewModel = viewModel,
+ viewModelFactory = viewModelFactory,
blueprints = sceneBlueprints,
clockInteractor = clockInteractor
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0b3d0f7..3f9c98d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2399,6 +2399,16 @@
*/
private void handleDismiss(IKeyguardDismissCallback callback, CharSequence message) {
if (mShowing) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ Log.d(TAG, "Dismissing keyguard with keyguard_wm_refactor_enabled: "
+ + "cancelDoKeyguardLaterLocked");
+
+ // This won't get canceled in onKeyguardExitFinished() if the refactor is enabled,
+ // which can lead to the keyguard re-showing. Cancel here for now; this can be
+ // removed once we migrate the logic that posts doKeyguardLater in the first place.
+ cancelDoKeyguardLaterLocked();
+ }
+
if (callback != null) {
mDismissCallbackRegistry.addCallback(callback);
}
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 edf17c1..81b0064 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
@@ -232,6 +232,9 @@
/** Receive an event for doze time tick */
val dozeTimeTick: Flow<Long>
+ /** Receive an event lockscreen being shown in a dismissible state */
+ val showDismissibleKeyguard: MutableStateFlow<Long>
+
/** Observable for DismissAction */
val dismissAction: StateFlow<DismissAction>
@@ -305,6 +308,8 @@
fun dozeTimeTick()
+ fun showDismissibleKeyguard()
+
fun setDismissAction(dismissAction: DismissAction)
suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
@@ -439,6 +444,12 @@
_dozeTimeTick.value = systemClock.uptimeMillis()
}
+ override val showDismissibleKeyguard = MutableStateFlow<Long>(0L)
+
+ override fun showDismissibleKeyguard() {
+ showDismissibleKeyguard.value = systemClock.uptimeMillis()
+ }
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3775d19..17c1e82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -133,7 +133,12 @@
transitionInteractor.startedKeyguardState.replayCache.last() ==
KeyguardState.DREAMING
) {
- startTransitionTo(KeyguardState.LOCKSCREEN)
+ if (powerInteractor.detailedWakefulness.value.isAwake()) {
+ startTransitionTo(
+ KeyguardState.LOCKSCREEN,
+ ownerReason = "Dream has ended and device is awake"
+ )
+ }
}
}
}
@@ -144,7 +149,7 @@
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isDreaming
+ keyguardInteractor.isAbleToDream
// Debounce the dreaming signal since there is a race condition between
// the occluded and dreaming signals. We therefore add a small delay
// to give enough time for occluded to flip to false when the dream
@@ -172,7 +177,7 @@
}
scope.launch {
- keyguardInteractor.isDreaming
+ keyguardInteractor.isAbleToDream
.filter { !it }
.sample(deviceEntryInteractor.isUnlocked, ::Pair)
.collect { (_, dismissable) ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 8f4110c..db5a63b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -74,6 +74,7 @@
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
listenForGoneToLockscreenOrHub()
+ listenForGoneToOccluded()
listenForGoneToDreamingLockscreenHosted()
}
@@ -81,6 +82,27 @@
scope.launch("$TAG#showKeyguard") { startTransitionTo(KeyguardState.LOCKSCREEN) }
}
+ /**
+ * A special case supported on foldables, where folding the device may put the device on an
+ * unlocked lockscreen, but if an occluding app is already showing (like a active phone call),
+ * then go directly to OCCLUDED.
+ */
+ private fun listenForGoneToOccluded() {
+ scope.launch("$TAG#listenForGoneToOccluded") {
+ keyguardInteractor.showDismissibleKeyguard
+ .filterRelevantKeyguardState()
+ .sample(keyguardInteractor.isKeyguardOccluded, ::Pair)
+ .collect { (_, isKeyguardOccluded) ->
+ if (isKeyguardOccluded) {
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "Dismissible keyguard with occlusion"
+ )
+ }
+ }
+ }
+ }
+
// Primarily for when the user chooses to lock down the device
private fun listenForGoneToLockscreenOrHub() {
if (KeyguardWmStateRefactor.isEnabled) {
@@ -166,11 +188,12 @@
interpolator = Interpolators.LINEAR
duration =
when (toState) {
- KeyguardState.DREAMING -> TO_DREAMING_DURATION
KeyguardState.AOD -> TO_AOD_DURATION
KeyguardState.DOZING -> TO_DOZING_DURATION
+ KeyguardState.DREAMING -> TO_DREAMING_DURATION
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+ KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -179,10 +202,11 @@
companion object {
private const val TAG = "FromGoneTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
- val TO_DREAMING_DURATION = 933.milliseconds
val TO_AOD_DURATION = 1300.milliseconds
val TO_DOZING_DURATION = 933.milliseconds
+ val TO_DREAMING_DURATION = 933.milliseconds
val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
+ val TO_OCCLUDED_DURATION = 100.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 51d92f0..5dc020f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -300,7 +300,9 @@
swipeToDismissInteractor.dismissFling
.filterNotNull()
.filterRelevantKeyguardState()
- .collect { _ -> startTransitionTo(KeyguardState.GONE) }
+ .collect { _ ->
+ startTransitionTo(KeyguardState.GONE, ownerReason = "dismissFling != null")
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 710b710a..aea57ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -157,6 +157,13 @@
}
}
+ /** Starts a transition to dismiss the keyguard from the OCCLUDED state. */
+ fun dismissFromOccluded() {
+ scope.launch {
+ startTransitionTo(KeyguardState.GONE, ownerReason = "Dismiss from occluded")
+ }
+ }
+
private fun listenForOccludedToGone() {
if (KeyguardWmStateRefactor.isEnabled) {
// We don't think OCCLUDED to GONE is possible. You should always have to go via a
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 42490c4..0df989e 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
@@ -1,20 +1,18 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.
*/
-
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.keyguard.domain.interactor
@@ -156,15 +154,24 @@
val isPulsing: Flow<Boolean> = dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }
- /**
- * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
- * but not vice-versa.
- */
- val isDreaming: StateFlow<Boolean> = repository.isDreaming
-
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+ /**
+ * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
+ * but not vice-versa. Also accounts for [isDreamingWithOverlay]
+ */
+ val isDreaming: StateFlow<Boolean> =
+ merge(
+ repository.isDreaming,
+ repository.isDreamingWithOverlay,
+ )
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
/** Whether the system is dreaming and the active dream is hosted in lockscreen */
val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
@@ -172,6 +179,9 @@
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
+ /** Event for when an unlocked keyguard has been requested, such as on device fold */
+ val showDismissibleKeyguard: Flow<Long> = repository.showDismissibleKeyguard.asStateFlow()
+
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
@@ -179,12 +189,25 @@
* Allow a brief moment to prevent rapidly oscillating between true/false signals.
*/
val isAbleToDream: Flow<Boolean> =
- merge(isDreaming, isDreamingWithOverlay)
- .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
- isDreaming && isDozeOff(dozeTransitionModel.to)
+ dozeTransitionModel
+ .flatMapLatest { dozeTransitionModel ->
+ if (isDozeOff(dozeTransitionModel.to)) {
+ // When dozing stops, it is a very early signal that the device is exiting the
+ // dream state. DreamManagerService eventually notifies window manager, which
+ // invokes SystemUI through KeyguardService. Because of this substantial delay,
+ // do not immediately process any dreaming information when exiting AOD. It
+ // should actually be quite strange to leave AOD and then go straight to
+ // DREAMING so this should be fine.
+ delay(500L)
+ isDreaming
+ .sample(powerInteractor.isAwake) { isDreaming, isAwake ->
+ isDreaming && isAwake
+ }
+ .debounce(50L)
+ } else {
+ flowOf(false)
+ }
}
- .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
- .debounce(50L)
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
@@ -469,6 +492,10 @@
CameraLaunchSourceModel(type = cameraLaunchSourceIntToType(source))
}
+ fun showDismissibleKeyguard() {
+ repository.showDismissibleKeyguard()
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index e132eb7..b89eb27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -98,6 +98,16 @@
}
scope.launch {
+ keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) }
+ }
+
+ scope.launch {
+ keyguardInteractor.isDreamingWithOverlay.collect {
+ logger.log(TAG, VERBOSE, "isDreamingWithOverlay", it)
+ }
+ }
+
+ scope.launch {
keyguardInteractor.isAbleToDream.collect {
logger.log(TAG, VERBOSE, "isAbleToDream", it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index afbe357..efdae62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -75,6 +75,7 @@
private val fromAlternateBouncerTransitionInteractor:
dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
+ private val fromOccludedTransitionInteractor: dagger.Lazy<FromOccludedTransitionInteractor>,
private val sceneInteractor: SceneInteractor,
) {
private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
@@ -418,6 +419,7 @@
fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
AOD -> fromAodTransitionInteractor.get().dismissAod()
DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
+ KeyguardState.OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
KeyguardState.GONE ->
Log.i(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index e1b333d..25b2b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -93,6 +93,14 @@
KeyguardState.ALTERNATE_BOUNCER -> {
fromAlternateBouncerInteractor.surfaceBehindVisibility
}
+ KeyguardState.OCCLUDED -> {
+ // OCCLUDED -> GONE occurs when an app is on top of the keyguard, and then
+ // requests manual dismissal of the keyguard in the background. The app will
+ // remain visible on top of the stack throughout this transition, so we
+ // should not trigger the keyguard going away animation by returning
+ // surfaceBehindVisibility = true.
+ flowOf(false)
+ }
else -> flowOf(null)
}
}
@@ -253,6 +261,18 @@
) {
// Dreams dismiss keyguard and return to GONE if they can.
false
+ } else if (
+ startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
+ startedWithPrev.newValue.to == KeyguardState.GONE
+ ) {
+ // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
+ // when an app uses intent flags to launch over an insecure keyguard without
+ // dismissing it, and then manually requests keyguard dismissal while
+ // OCCLUDED. This transition is not user-visible; the device unlocks in the
+ // background and the app remains on top, while we're now GONE. In this case
+ // we should simply tell WM that the lockscreen is no longer visible, and
+ // *not* play the going away animation or related animations.
+ false
} else {
// Otherwise, use the visibility of the current state.
KeyguardState.lockscreenVisibleInState(currentState)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 162a0d2..15e6b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,18 +30,23 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.doOnEnd
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
@@ -49,11 +54,20 @@
import kotlinx.coroutines.launch
/** This is only for a SINGLE Quick affordance */
-object KeyguardQuickAffordanceViewBinder {
+@SysUISingleton
+class KeyguardQuickAffordanceViewBinder
+@Inject
+constructor(
+ private val falsingManager: FalsingManager?,
+ private val vibratorHelper: VibratorHelper?,
+ private val logger: KeyguardQuickAffordancesLogger,
+ @Main private val mainImmediateDispatcher: CoroutineDispatcher,
+) {
- private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
- private const val SCALE_SELECTED_BUTTON = 1.23f
- private const val DIM_ALPHA = 0.3f
+ private val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+ private val SCALE_SELECTED_BUTTON = 1.23f
+ private val DIM_ALPHA = 0.3f
+ private val TAG = "KeyguardQuickAffordanceViewBinder"
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
@@ -73,30 +87,24 @@
view: LaunchableImageView,
viewModel: Flow<KeyguardQuickAffordanceViewModel>,
alpha: Flow<Float>,
- falsingManager: FalsingManager?,
- vibratorHelper: VibratorHelper?,
- logger: KeyguardQuickAffordancesLogger,
messageDisplayer: (Int) -> Unit,
): Binding {
val button = view as ImageView
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
- view.repeatWhenAttached {
+ view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ launch("$TAG#viewModel") {
viewModel.collect { buttonModel ->
updateButton(
view = button,
viewModel = buttonModel,
- falsingManager = falsingManager,
messageDisplayer = messageDisplayer,
- vibratorHelper = vibratorHelper,
- logger = logger,
)
}
}
- launch {
+ launch("$TAG#updateButtonAlpha") {
updateButtonAlpha(
view = button,
viewModel = viewModel,
@@ -104,7 +112,7 @@
)
}
- launch {
+ launch("$TAG#configurationBasedDimensions") {
configurationBasedDimensions.collect { dimensions ->
button.updateLayoutParams<ViewGroup.LayoutParams> {
width = dimensions.buttonSizePx.width
@@ -131,10 +139,7 @@
private fun updateButton(
view: ImageView,
viewModel: KeyguardQuickAffordanceViewModel,
- falsingManager: FalsingManager?,
messageDisplayer: (Int) -> Unit,
- vibratorHelper: VibratorHelper?,
- logger: KeyguardQuickAffordancesLogger,
) {
if (!viewModel.isVisible) {
view.isInvisible = true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 91b66c3..aab5b9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -70,7 +70,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -292,20 +291,18 @@
}
}
- if (NotificationIconContainerRefactor.isEnabled) {
- launch {
- val iconsAppearTranslationPx =
- configuration
- .getDimensionPixelSize(R.dimen.shelf_appear_translation)
- .stateIn(this)
- viewModel.isNotifIconContainerVisible.collect { isVisible ->
- childViews[aodNotificationIconContainerId]
- ?.setAodNotifIconContainerIsVisible(
- isVisible,
- iconsAppearTranslationPx.value,
- screenOffAnimationController,
- )
- }
+ launch {
+ val iconsAppearTranslationPx =
+ configuration
+ .getDimensionPixelSize(R.dimen.shelf_appear_translation)
+ .stateIn(this)
+ viewModel.isNotifIconContainerVisible.collect { isVisible ->
+ childViews[aodNotificationIconContainerId]
+ ?.setAodNotifIconContainerIsVisible(
+ isVisible,
+ iconsAppearTranslationPx.value,
+ screenOffAnimationController,
+ )
}
}
@@ -519,7 +516,6 @@
if (MigrateClocksToBlueprint.isEnabled) {
throw IllegalStateException("should only be called in legacy code paths")
}
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return
coroutineScope {
val iconAppearTranslationPx =
configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 6faca1e..6031ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -50,7 +50,6 @@
import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -79,7 +78,6 @@
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -89,7 +87,6 @@
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -133,8 +130,6 @@
private val broadcastDispatcher: BroadcastDispatcher,
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
- private val falsingManager: FalsingManager,
- private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val keyguardRootViewModel: KeyguardRootViewModel,
private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
@@ -148,7 +143,7 @@
private val defaultShortcutsSection: DefaultShortcutsSection,
private val keyguardClockInteractor: KeyguardClockInteractor,
private val keyguardClockViewModel: KeyguardClockViewModel,
- private val quickAffordancesLogger: KeyguardQuickAffordancesLogger,
+ private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -458,13 +453,10 @@
keyguardRootView.findViewById<LaunchableImageView?>(R.id.start_button)?.let { imageView ->
shortcutsBindings.add(
- KeyguardQuickAffordanceViewBinder.bind(
+ keyguardQuickAffordanceViewBinder.bind(
view = imageView,
viewModel = quickAffordancesCombinedViewModel.startButton,
alpha = flowOf(1f),
- falsingManager = falsingManager,
- vibratorHelper = vibratorHelper,
- logger = quickAffordancesLogger,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -473,13 +465,10 @@
keyguardRootView.findViewById<LaunchableImageView?>(R.id.end_button)?.let { imageView ->
shortcutsBindings.add(
- KeyguardQuickAffordanceViewBinder.bind(
+ keyguardQuickAffordanceViewBinder.bind(
view = imageView,
viewModel = quickAffordancesCombinedViewModel.endButton,
alpha = flowOf(1f),
- falsingManager = falsingManager,
- vibratorHelper = vibratorHelper,
- logger = quickAffordancesLogger,
) { message ->
indicationController.showTransientIndication(message)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
index 2dc9301..bf6f2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -42,12 +42,6 @@
): KeyguardBlueprint
@Binds
- @IntoSet
- abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
- shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
- ): KeyguardBlueprint
-
- @Binds
@IntoMap
@ClassKey(KeyguardBlueprintInteractor::class)
abstract fun bindsKeyguardBlueprintInteractor(
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
deleted file mode 100644
index b984a68..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ /dev/null
@@ -1,94 +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.keyguard.ui.view.layout.blueprints
-
-import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
-import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
-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.ClockSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-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
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection
-import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
-import com.android.systemui.util.kotlin.getOrNull
-import java.util.Optional
-import javax.inject.Inject
-import javax.inject.Named
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/** Vertically aligns the shortcuts with the udfps. */
-@ExperimentalCoroutinesApi
-@SysUISingleton
-class ShortcutsBesideUdfpsKeyguardBlueprint
-@Inject
-constructor(
- accessibilityActionsSection: AccessibilityActionsSection,
- alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
- defaultIndicationAreaSection: DefaultIndicationAreaSection,
- defaultDeviceEntrySection: DefaultDeviceEntrySection,
- @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
- defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
- defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
- defaultStatusViewSection: DefaultStatusViewSection,
- defaultStatusBarSection: DefaultStatusBarSection,
- defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
- aodNotificationIconsSection: AodNotificationIconsSection,
- aodBurnInSection: AodBurnInSection,
- communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
- clockSection: ClockSection,
- smartspaceSection: SmartspaceSection,
- keyguardSliceViewSection: KeyguardSliceViewSection,
- udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
-) : KeyguardBlueprint {
- override val id: String = SHORTCUTS_BESIDE_UDFPS
-
- override val sections =
- listOfNotNull(
- accessibilityActionsSection,
- defaultIndicationAreaSection,
- alignShortcutsToUdfpsSection,
- defaultAmbientIndicationAreaSection.getOrNull(),
- defaultSettingsPopupMenuSection,
- defaultStatusViewSection,
- defaultStatusBarSection,
- defaultNotificationStackScrollLayoutSection,
- aodNotificationIconsSection,
- smartspaceSection,
- aodBurnInSection,
- communalTutorialIndicatorSection,
- clockSection,
- keyguardSliceViewSection,
- defaultDeviceEntrySection,
- udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others
- )
-
- companion object {
- const val SHORTCUTS_BESIDE_UDFPS = "shortcuts-besides-udfps"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
deleted file mode 100644
index 1ba830b..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ /dev/null
@@ -1,114 +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.keyguard.ui.view.layout.sections
-
-import android.content.res.Resources
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.LEFT
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.RIGHT
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
-import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
-import javax.inject.Inject
-
-class AlignShortcutsToUdfpsSection
-@Inject
-constructor(
- @Main private val resources: Resources,
- private val keyguardQuickAffordancesCombinedViewModel:
- KeyguardQuickAffordancesCombinedViewModel,
- private val keyguardRootViewModel: KeyguardRootViewModel,
- private val falsingManager: FalsingManager,
- private val indicationController: KeyguardIndicationController,
- private val vibratorHelper: VibratorHelper,
- private val shortcutsLogger: KeyguardQuickAffordancesLogger,
-) : BaseShortcutSection() {
- override fun addViews(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- addLeftShortcut(constraintLayout)
- addRightShortcut(constraintLayout)
- }
- }
-
- override fun bindData(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- leftShortcutHandle =
- KeyguardQuickAffordanceViewBinder.bind(
- constraintLayout.requireViewById(R.id.start_button),
- keyguardQuickAffordancesCombinedViewModel.startButton,
- keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
- falsingManager,
- vibratorHelper,
- shortcutsLogger,
- ) {
- indicationController.showTransientIndication(it)
- }
- rightShortcutHandle =
- KeyguardQuickAffordanceViewBinder.bind(
- constraintLayout.requireViewById(R.id.end_button),
- keyguardQuickAffordancesCombinedViewModel.endButton,
- keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
- falsingManager,
- vibratorHelper,
- shortcutsLogger,
- ) {
- indicationController.showTransientIndication(it)
- }
- }
- }
-
- override fun applyConstraints(constraintSet: ConstraintSet) {
- val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
- val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
-
- val lockIconViewId =
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- R.id.device_entry_icon_view
- } else {
- R.id.lock_icon_view
- }
-
- constraintSet.apply {
- constrainWidth(R.id.start_button, width)
- constrainHeight(R.id.start_button, height)
- connect(R.id.start_button, LEFT, PARENT_ID, LEFT)
- connect(R.id.start_button, RIGHT, lockIconViewId, LEFT)
- connect(R.id.start_button, TOP, lockIconViewId, TOP)
- connect(R.id.start_button, BOTTOM, lockIconViewId, BOTTOM)
-
- constrainWidth(R.id.end_button, width)
- constrainHeight(R.id.end_button, height)
- connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT)
- connect(R.id.end_button, LEFT, lockIconViewId, RIGHT)
- connect(R.id.end_button, TOP, lockIconViewId, TOP)
- connect(R.id.end_button, BOTTOM, lockIconViewId, BOTTOM)
- }
- }
-}
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 d77b548..36ef78e 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
@@ -37,8 +37,6 @@
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.ui.SystemBarUtilsState
import com.android.systemui.util.ui.value
@@ -53,7 +51,6 @@
private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
- private val notificationIconAreaController: NotificationIconAreaController,
private val systemBarUtilsState: SystemBarUtilsState,
private val rootViewModel: KeyguardRootViewModel,
) : KeyguardSection() {
@@ -86,20 +83,16 @@
return
}
- if (NotificationIconContainerRefactor.isEnabled) {
- nicBindingDisposable?.dispose()
- nicBindingDisposable =
- NotificationIconContainerViewBinder.bindWhileAttached(
- nic,
- nicAodViewModel,
- configurationState,
- systemBarUtilsState,
- iconBindingFailureTracker,
- nicAodIconViewStore,
- )
- } else {
- notificationIconAreaController.setupAodIcons(nic)
- }
+ nicBindingDisposable?.dispose()
+ nicBindingDisposable =
+ NotificationIconContainerViewBinder.bindWhileAttached(
+ nic,
+ nicAodViewModel,
+ configurationState,
+ systemBarUtilsState,
+ iconBindingFailureTracker,
+ nicAodIconViewStore,
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 380e361..6ac33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -24,7 +24,6 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
@@ -64,13 +63,7 @@
val useLargeScreenHeader =
context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)
val marginTopLargeScreen =
- if (centralizedStatusBarHeightFix()) {
- largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
- } else {
- context.resources.getDimensionPixelSize(
- R.dimen.large_screen_shade_header_height
- )
- }
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
connect(
R.id.nssl_placeholder,
TOP,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 64c46db..e558033 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -26,7 +26,6 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -35,10 +34,8 @@
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
-import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
@@ -49,11 +46,9 @@
private val keyguardQuickAffordancesCombinedViewModel:
KeyguardQuickAffordancesCombinedViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
- private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
- private val vibratorHelper: VibratorHelper,
private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
- private val shortcutsLogger: KeyguardQuickAffordancesLogger,
+ private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
) : BaseShortcutSection() {
// Amount to increase the bottom margin by to avoid colliding with inset
@@ -82,24 +77,18 @@
override fun bindData(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
leftShortcutHandle =
- KeyguardQuickAffordanceViewBinder.bind(
+ keyguardQuickAffordanceViewBinder.bind(
constraintLayout.requireViewById(R.id.start_button),
keyguardQuickAffordancesCombinedViewModel.startButton,
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
- falsingManager,
- vibratorHelper,
- shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
rightShortcutHandle =
- KeyguardQuickAffordanceViewBinder.bind(
+ keyguardQuickAffordanceViewBinder.bind(
constraintLayout.requireViewById(R.id.end_button),
keyguardQuickAffordancesCombinedViewModel.endButton,
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
- falsingManager,
- vibratorHelper,
- shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
index c590f07..992550c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
@@ -47,6 +48,15 @@
edge = Edge.create(from = ALTERNATE_BOUNCER, to = AOD),
)
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ return transitionAnimation.sharedFlow(
+ duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+ onStart = { startAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(startAlpha, 1f, it) },
+ )
+ }
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 5a559fc..470f17b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -42,9 +42,6 @@
val transitionToAlternateBouncerProgress =
keyguardTransitionInteractor.transitionValue(ALTERNATE_BOUNCER)
- val forcePluginOpen: Flow<Boolean> =
- transitionToAlternateBouncerProgress.map { it > 0f }.distinctUntilChanged()
-
/** An observable for the scrim alpha. */
val scrimAlpha = transitionToAlternateBouncerProgress.map { it * alternateBouncerScrimAlpha }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 680f966..2426f97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -19,6 +19,7 @@
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.FlowTracing.traceEmissionCount
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,19 +30,23 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardQuickAffordancesCombinedViewModel
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
private val keyguardInteractor: KeyguardInteractor,
shadeInteractor: ShadeInteractor,
@@ -133,9 +138,14 @@
/** The source of truth of alpha for all of the quick affordances on lockscreen */
val transitionAlpha: Flow<Float> =
merge(
- fadeInAlpha,
- fadeOutAlpha,
- )
+ fadeInAlpha,
+ fadeOutAlpha,
+ )
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 0f,
+ )
/**
* Whether quick affordances are "opaque enough" to be considered visible to and interactive by
@@ -199,38 +209,42 @@
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return previewMode.flatMapLatest { previewMode ->
- combine(
- if (previewMode.isInPreviewMode) {
- quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
- } else {
- quickAffordanceInteractor.quickAffordance(position = position)
- },
- keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
- areQuickAffordancesFullyOpaque,
- selectedPreviewSlotId,
- quickAffordanceInteractor.useLongPress(),
- ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
- val slotId = position.toSlotId()
- val isSelected = selectedPreviewSlotId == slotId
- model.toViewModel(
- animateReveal = !previewMode.isInPreviewMode && animateReveal,
- isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
- isSelected =
- previewMode.isInPreviewMode &&
- previewMode.shouldHighlightSelectedAffordance &&
- isSelected,
- isDimmed =
- previewMode.isInPreviewMode &&
- previewMode.shouldHighlightSelectedAffordance &&
- !isSelected,
- forceInactive = previewMode.isInPreviewMode,
- slotId = slotId,
- useLongPress = useLongPress,
- )
- }
- .distinctUntilChanged()
- }.traceEmissionCount({"QuickAfforcances#button${position.toSlotId()}"})
+ return previewMode
+ .flatMapLatest { previewMode ->
+ combine(
+ if (previewMode.isInPreviewMode) {
+ quickAffordanceInteractor.quickAffordanceAlwaysVisible(
+ position = position
+ )
+ } else {
+ quickAffordanceInteractor.quickAffordance(position = position)
+ },
+ keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
+ areQuickAffordancesFullyOpaque,
+ selectedPreviewSlotId,
+ quickAffordanceInteractor.useLongPress(),
+ ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
+ val slotId = position.toSlotId()
+ val isSelected = selectedPreviewSlotId == slotId
+ model.toViewModel(
+ animateReveal = !previewMode.isInPreviewMode && animateReveal,
+ isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
+ isSelected =
+ previewMode.isInPreviewMode &&
+ previewMode.shouldHighlightSelectedAffordance &&
+ isSelected,
+ isDimmed =
+ previewMode.isInPreviewMode &&
+ previewMode.shouldHighlightSelectedAffordance &&
+ !isSelected,
+ forceInactive = previewMode.isInPreviewMode,
+ slotId = slotId,
+ useLongPress = useLongPress,
+ )
+ }
+ .distinctUntilChanged()
+ }
+ .traceEmissionCount({ "QuickAfforcances#button${position.toSlotId()}" })
}
private fun KeyguardQuickAffordanceModel.toViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 38a2b1b..050ef6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -83,6 +83,7 @@
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+ private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val alternateBouncerToLockscreenTransitionViewModel:
@@ -239,6 +240,7 @@
merge(
alphaOnShadeExpansion,
keyguardInteractor.dismissAlpha,
+ alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState),
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3fffeff..59cb6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -20,40 +20,43 @@
import com.android.compose.animation.scene.ContentKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
-import javax.inject.Inject
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
-@SysUISingleton
class LockscreenContentViewModel
-@Inject
+@AssistedInject
constructor(
clockInteractor: KeyguardClockInteractor,
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
val touchHandling: KeyguardTouchHandlingViewModel,
- val shadeInteractor: ShadeInteractor,
- @Application private val applicationScope: CoroutineScope,
- unfoldTransitionInteractor: UnfoldTransitionInteractor,
- occlusionInteractor: SceneContainerOcclusionInteractor,
-) {
+ private val shadeInteractor: ShadeInteractor,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ private val occlusionInteractor: SceneContainerOcclusionInteractor,
+) : SysUiViewModel() {
@VisibleForTesting val clockSize = clockInteractor.clockSize
val isUdfpsVisible: Boolean
@@ -61,32 +64,36 @@
val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
+ private val _unfoldTranslations = MutableStateFlow(UnfoldTranslations())
/** Amount of horizontal translation that should be applied to elements in the scene. */
- val unfoldTranslations: StateFlow<UnfoldTranslations> =
- combine(
- unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
- unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
- ) { start, end ->
- UnfoldTranslations(
- start = start,
- end = end,
- )
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = UnfoldTranslations(),
- )
+ val unfoldTranslations: StateFlow<UnfoldTranslations> = _unfoldTranslations.asStateFlow()
+ private val _isContentVisible = MutableStateFlow(true)
/** Whether the content of the scene UI should be shown. */
- val isContentVisible: StateFlow<Boolean> =
- occlusionInteractor.isOccludingActivityShown
- .map { !it }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = true,
- )
+ val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow()
+
+ override suspend fun onActivated() {
+ coroutineScope {
+ launch {
+ combine(
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ UnfoldTranslations(
+ start = start,
+ end = end,
+ )
+ }
+ .collectLatest { _unfoldTranslations.value = it }
+ }
+
+ launch {
+ occlusionInteractor.isOccludingActivityShown
+ .map { !it }
+ .collectLatest { _isContentVisible.value = it }
+ }
+ }
+ }
/**
* Returns a flow that indicates whether lockscreen notifications should be rendered in the
@@ -142,4 +149,9 @@
*/
val end: Float = 0f,
)
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): LockscreenContentViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
new file mode 100644
index 0000000..7383f57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.util.kotlin.filterValuesNotNull
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Models UI state and handles user input for the lockscreen scene. */
+class LockscreenSceneActionsViewModel
+@AssistedInject
+constructor(
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val communalInteractor: CommunalInteractor,
+ private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ shadeInteractor.isShadeTouchable
+ .flatMapLatest { isShadeTouchable ->
+ if (!isShadeTouchable) {
+ flowOf(emptyMap())
+ } else {
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ val notifShadeSceneKey =
+ UserActionResult(
+ toScene = SceneFamilies.NotifShade,
+ transitionKey =
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
+ )
+
+ mapOf(
+ Swipe.Left to
+ UserActionResult(Scenes.Communal).takeIf {
+ isCommunalAvailable
+ },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split
+ // shade mode).
+ swipeDownFromTop(pointerCount = 1) to
+ if (shadeMode is ShadeMode.Single) {
+ UserActionResult(Scenes.QuickSettings)
+ } else {
+ notifShadeSceneKey
+ },
+
+ // TODO(b/338577208): Remove once we add Dual Shade invocation zones
+ swipeDownFromTop(pointerCount = 2) to
+ UserActionResult(
+ toScene = SceneFamilies.QuickSettings,
+ transitionKey =
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ ),
+
+ // Swiping down, not from the edge, always navigates to the notif
+ // shade scene.
+ swipeDown(pointerCount = 1) to notifShadeSceneKey,
+ swipeDown(pointerCount = 2) to notifShadeSceneKey,
+ )
+ .filterValuesNotNull()
+ }
+ }
+ }
+ .collectLatest { setActions(it) }
+ }
+
+ private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top,
+ pointerCount = pointerCount,
+ )
+ }
+
+ private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ pointerCount = pointerCount,
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): LockscreenSceneActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
deleted file mode 100644
index 15892e9..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ /dev/null
@@ -1,116 +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.
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.util.kotlin.filterValuesNotNull
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-
-/** Models UI state and handles user input for the lockscreen scene. */
-@SysUISingleton
-class LockscreenSceneViewModel
-@Inject
-constructor(
- private val deviceEntryInteractor: DeviceEntryInteractor,
- private val communalInteractor: CommunalInteractor,
- private val shadeInteractor: ShadeInteractor,
- val touchHandling: KeyguardTouchHandlingViewModel,
- val notifications: NotificationsPlaceholderViewModel,
-) {
- val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- shadeInteractor.isShadeTouchable.flatMapLatest { isShadeTouchable ->
- if (!isShadeTouchable) {
- flowOf(emptyMap())
- } else {
- combine(
- deviceEntryInteractor.isUnlocked,
- communalInteractor.isCommunalAvailable,
- shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
- val notifShadeSceneKey =
- UserActionResult(
- toScene = SceneFamilies.NotifShade,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
- )
-
- mapOf(
- Swipe.Left to
- UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
- Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
-
- // Swiping down from the top edge goes to QS (or shade if in split shade
- // mode).
- swipeDownFromTop(pointerCount = 1) to
- if (shadeMode is ShadeMode.Single) {
- UserActionResult(Scenes.QuickSettings)
- } else {
- notifShadeSceneKey
- },
-
- // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
- swipeDownFromTop(pointerCount = 2) to
- UserActionResult(
- toScene = SceneFamilies.QuickSettings,
- transitionKey =
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- ),
-
- // Swiping down, not from the edge, always navigates to the notif shade
- // scene.
- swipeDown(pointerCount = 1) to notifShadeSceneKey,
- swipeDown(pointerCount = 2) to notifShadeSceneKey,
- )
- .filterValuesNotNull()
- }
- }
- }
-
- private fun swipeDownFromTop(pointerCount: Int): Swipe {
- return Swipe(
- SwipeDirection.Down,
- fromSource = Edge.Top,
- pointerCount = pointerCount,
- )
- }
-
- private fun swipeDown(pointerCount: Int): Swipe {
- return Swipe(
- SwipeDirection.Down,
- pointerCount = pointerCount,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
index 133d14d..111a2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/buttons/KeyButtonView.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
@@ -49,6 +50,7 @@
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.Flags;
import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
@@ -231,15 +233,28 @@
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- if (mCode != KEYCODE_UNKNOWN) {
+ if (isClickable()) {
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
if (isLongClickable()) {
info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
+ new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK,
+ getAccessibilityLongClickActionLabel()));
}
}
}
+ /**
+ * Gets the accessibility long click action label for the button, or {@code null} for no label.
+ */
+ @Nullable
+ private CharSequence getAccessibilityLongClickActionLabel() {
+ if (Flags.imeSwitcherRevamp() && getId() == R.id.ime_switcher) {
+ return getContext().getText(
+ com.android.internal.R.string.input_method_ime_switch_long_click_action_desc);
+ }
+ return null;
+ }
+
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
index fa8e13a..2d2b869 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
@@ -20,23 +20,27 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
-/** Models UI state and handles user input for the Notifications Shade scene. */
-@SysUISingleton
-class NotificationsShadeSceneViewModel
-@Inject
+/**
+ * Models the UI state for the user actions that the user can perform to navigate to other scenes.
+ *
+ * Different from the [NotificationsShadeSceneContentViewModel] which models the _content_ of the
+ * scene.
+ */
+class NotificationsShadeSceneActionsViewModel
+@AssistedInject
constructor(
- shadeInteractor: ShadeInteractor,
-) {
- val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- flowOf(
+ private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ setActions(
mapOf(
if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
Swipe.Up
@@ -46,4 +50,10 @@
Back to SceneFamilies.Home,
)
)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): NotificationsShadeSceneActionsViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt
new file mode 100644
index 0000000..c1c7320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.notifications.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.shade.ui.viewmodel.BaseShadeSceneViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Models UI state used to render the content of the notifications shade scene.
+ *
+ * Different from [NotificationsShadeSceneActionsViewModel], which only models user actions that can
+ * be performed to navigate to other scenes.
+ */
+class NotificationsShadeSceneContentViewModel
+@AssistedInject
+constructor(
+ deviceEntryInteractor: DeviceEntryInteractor,
+ sceneInteractor: SceneInteractor,
+) :
+ BaseShadeSceneViewModel(
+ deviceEntryInteractor,
+ sceneInteractor,
+ ) {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): NotificationsShadeSceneContentViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index db0676e..9939075 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -18,8 +18,6 @@
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
@@ -194,12 +192,7 @@
QuickStatusBarHeaderController quickStatusBarHeaderController) {
int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) {
- topPadding =
- centralizedStatusBarHeightFix()
- ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
- : mContext.getResources()
- .getDimensionPixelSize(
- R.dimen.large_screen_shade_header_height);
+ topPadding = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
}
if (mQSPanelContainer != null) {
mQSPanelContainer.setPaddingRelative(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 9c88eb9..5a3f1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,8 +17,6 @@
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
-
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -100,10 +98,7 @@
qqsLP.topMargin = mContext.getResources()
.getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
} else {
- qqsLP.topMargin = centralizedStatusBarHeightFix()
- ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)
- : mContext.getResources()
- .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height);
+ qqsLP.topMargin = LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext);
}
mHeaderQsPanel.setLayoutParams(qqsLP);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
index 1b34c33..89be17b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java
@@ -19,6 +19,7 @@
import android.database.ContentObserver;
import android.os.Handler;
+import com.android.systemui.Flags;
import com.android.systemui.statusbar.policy.Listenable;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
@@ -76,10 +77,20 @@
mListening = listening;
if (listening) {
mObservedValue = getValueFromProvider();
- mSettingsProxy.registerContentObserverForUserSync(
- mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+ if (Flags.qsRegisterSettingObserverOnBgThread()) {
+ mSettingsProxy.registerContentObserverForUserAsync(
+ mSettingsProxy.getUriFor(mSettingName), this, mUserId, () ->
+ mObservedValue = getValueFromProvider());
+ } else {
+ mSettingsProxy.registerContentObserverForUserSync(
+ mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
+ }
} else {
- mSettingsProxy.unregisterContentObserverSync(this);
+ if (Flags.qsRegisterSettingObserverOnBgThread()) {
+ mSettingsProxy.unregisterContentObserverAsync(this);
+ } else {
+ mSettingsProxy.unregisterContentObserverSync(this);
+ }
mObservedValue = mDefaultValue;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
index 664d496..8772c51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QSComposeFragment.kt
@@ -33,7 +33,7 @@
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.qsUiRefactorComposeFragment() && NewQsUI.isEnabled
+ get() = Flags.qsUiRefactorComposeFragment()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index b18358c..6dcdea9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -40,15 +40,15 @@
@Application private val applicationScope: CoroutineScope
) {
- private val largeTilesSpecs =
+ val largeTilesSpecs =
preferencesInteractor.largeTilesSpecs
.onEach { logChange(it) }
.stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
- fun resize(spec: TileSpec, toIcon: Boolean) {
- if (toIcon) {
+ fun resize(spec: TileSpec) {
+ if (largeTilesSpecs.value.contains(spec)) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
} else {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index 0fe79af..874b3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -19,6 +19,7 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -38,17 +39,12 @@
override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
val newTiles: MutableList<TileSpec> = mutableListOf()
val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
- val tilesQueue =
+ val tilesQueue: ArrayDeque<SizedTile<TileSpec>> =
ArrayDeque(
tiles.map {
- SizedTile(
+ SizedTileImpl(
it,
- width =
- if (iconTilesInteractor.isIconTile(it)) {
- 1
- } else {
- 2
- }
+ if (iconTilesInteractor.isIconTile(it)) 1 else 2,
)
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
index 7e4381b..17b73a2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -17,7 +17,17 @@
package com.android.systemui.qs.panels.shared.model
/** Represents a tile of type [T] associated with a width */
-data class SizedTile<T>(val tile: T, val width: Int)
+interface SizedTile<T> {
+ val tile: T
+ val width: Int
+ val isIcon: Boolean
+ get() = width == 1
+}
+
+data class SizedTileImpl<T>(
+ override val tile: T,
+ override val width: Int,
+) : SizedTile<T>
/** Represents a row of [SizedTile] with a maximum width of [columns] */
class TileRow<T>(private val columns: Int) {
@@ -51,3 +61,26 @@
fun isFull(): Boolean = availableColumns == 0
}
+
+/**
+ * Converts a list of [SizedTile] to a sequence of rows based on the number of columns of the grid
+ */
+fun <T> splitInRowsSequence(
+ tiles: List<SizedTile<T>>,
+ columns: Int,
+): Sequence<List<SizedTile<T>>> = sequence {
+ val row = TileRow<T>(columns)
+ for (tile in tiles) {
+ check(tile.width <= columns)
+ if (!row.maybeAddTile(tile)) {
+ // Couldn't add tile to previous row, create a row with the current tiles
+ // and start a new one
+ yield(row.tiles)
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ if (row.tiles.isNotEmpty()) {
+ yield(row.tiles)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 71deeb6..2c57813 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -29,13 +29,14 @@
import androidx.compose.ui.draganddrop.DragAndDropTarget
import androidx.compose.ui.draganddrop.DragAndDropTransferData
import androidx.compose.ui.draganddrop.mimeTypes
+import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@Composable
fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
- val sourceSpec: MutableState<EditTileViewModel?> = remember { mutableStateOf(null) }
- return remember(listState) { DragAndDropState(sourceSpec, listState) }
+ val draggedCell: MutableState<SizedTile<EditTileViewModel>?> = remember { mutableStateOf(null) }
+ return remember(listState) { DragAndDropState(draggedCell, listState) }
}
/**
@@ -43,37 +44,37 @@
* drop events.
*/
class DragAndDropState(
- val sourceSpec: MutableState<EditTileViewModel?>,
- private val listState: EditTileListState
+ val draggedCell: MutableState<SizedTile<EditTileViewModel>?>,
+ private val listState: EditTileListState,
) {
val dragInProgress: Boolean
- get() = sourceSpec.value != null
+ get() = draggedCell.value != null
/** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
fun currentPosition(): Int {
- return sourceSpec.value?.let { listState.indexOf(it.tileSpec) } ?: -1
+ return draggedCell.value?.let { listState.indexOf(it.tile.tileSpec) } ?: -1
}
fun isMoving(tileSpec: TileSpec): Boolean {
- return sourceSpec.value?.let { it.tileSpec == tileSpec } ?: false
+ return draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
}
- fun onStarted(tile: EditTileViewModel) {
- sourceSpec.value = tile
+ fun onStarted(cell: SizedTile<EditTileViewModel>) {
+ draggedCell.value = cell
}
fun onMoved(targetSpec: TileSpec) {
- sourceSpec.value?.let { listState.move(it, targetSpec) }
+ draggedCell.value?.let { listState.move(it, targetSpec) }
}
fun movedOutOfBounds() {
// Removing the tiles from the current tile grid if it moves out of bounds. This clears
// the spacer and makes it apparent that dropping the tile at that point would remove it.
- sourceSpec.value?.let { listState.remove(it.tileSpec) }
+ draggedCell.value?.let { listState.remove(it.tile.tileSpec) }
}
fun onDrop() {
- sourceSpec.value = null
+ draggedCell.value = null
}
}
@@ -97,8 +98,8 @@
remember(dragAndDropState) {
object : DragAndDropTarget {
override fun onDrop(event: DragAndDropEvent): Boolean {
- return dragAndDropState.sourceSpec.value?.let {
- onDrop(it.tileSpec, dragAndDropState.currentPosition())
+ return dragAndDropState.draggedCell.value?.let {
+ onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
dragAndDropState.onDrop()
true
} ?: false
@@ -112,7 +113,7 @@
return dragAndDropTarget(
shouldStartDragAndDrop = { event ->
event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
- dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
+ dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
},
target = target,
)
@@ -134,8 +135,8 @@
remember(dragAndDropState) {
object : DragAndDropTarget {
override fun onDrop(event: DragAndDropEvent): Boolean {
- return dragAndDropState.sourceSpec.value?.let {
- onDrop(it.tileSpec)
+ return dragAndDropState.draggedCell.value?.let {
+ onDrop(it.tile.tileSpec)
dragAndDropState.onDrop()
true
} ?: false
@@ -176,8 +177,8 @@
}
override fun onDrop(event: DragAndDropEvent): Boolean {
- return dragAndDropState.sourceSpec.value?.let {
- onDrop(it.tileSpec, dragAndDropState.currentPosition())
+ return dragAndDropState.draggedCell.value?.let {
+ onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
dragAndDropState.onDrop()
true
} ?: false
@@ -188,23 +189,23 @@
target = target,
shouldStartDragAndDrop = { event ->
event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
- dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
+ dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
},
)
}
fun Modifier.dragAndDropTileSource(
- tile: EditTileViewModel,
+ sizedTile: SizedTile<EditTileViewModel>,
onTap: (TileSpec) -> Unit,
onDoubleTap: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState
): Modifier {
return dragAndDropSource {
detectTapGestures(
- onTap = { onTap(tile.tileSpec) },
- onDoubleTap = { onDoubleTap(tile.tileSpec) },
+ onTap = { onTap(sizedTile.tile.tileSpec) },
+ onDoubleTap = { onDoubleTap(sizedTile.tile.tileSpec) },
onLongPress = {
- dragAndDropState.onStarted(tile)
+ dragAndDropState.onStarted(sizedTile)
// The tilespec from the ClipData transferred isn't actually needed as we're moving
// a tile within the same application. We're using a custom MIME type to limit the
@@ -214,7 +215,7 @@
ClipData(
QsDragAndDrop.CLIPDATA_LABEL,
arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
- ClipData.Item(tile.tileSpec.spec)
+ ClipData.Item(sizedTile.tile.tileSpec.spec)
)
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index e0fed28..fa3008e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -20,22 +20,23 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
+import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@Composable
fun rememberEditListState(
- tiles: List<EditTileViewModel>,
+ tiles: List<SizedTile<EditTileViewModel>>,
): EditTileListState {
return remember(tiles) { EditTileListState(tiles) }
}
/** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<EditTileViewModel>) {
- val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList()
+class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>) {
+ val tiles: SnapshotStateList<SizedTile<EditTileViewModel>> = tiles.toMutableStateList()
- fun move(tile: EditTileViewModel, target: TileSpec) {
- val fromIndex = indexOf(tile.tileSpec)
+ fun move(sizedTile: SizedTile<EditTileViewModel>, target: TileSpec) {
+ val fromIndex = indexOf(sizedTile.tile.tileSpec)
val toIndex = indexOf(target)
if (toIndex == -1 || fromIndex == toIndex) {
@@ -44,7 +45,7 @@
if (fromIndex == -1) {
// If tile isn't in the list, simply insert it
- tiles.add(toIndex, tile)
+ tiles.add(toIndex, sizedTile)
} else {
// If tile is present in the list, move it
tiles.apply { add(toIndex, removeAt(fromIndex)) }
@@ -52,10 +53,10 @@
}
fun remove(tileSpec: TileSpec) {
- tiles.removeIf { it.tileSpec == tileSpec }
+ tiles.removeIf { it.tile.tileSpec == tileSpec }
}
fun indexOf(tileSpec: TileSpec): Int {
- return tiles.indexOfFirst { it.tileSpec == tileSpec }
+ return tiles.indexOfFirst { it.tile.tileSpec == tileSpec }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index add830e..bd925fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -22,12 +22,12 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
@@ -56,13 +56,14 @@
onDispose { tiles.forEach { it.stopListening(token) } }
}
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
+ val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
- items(tiles.size, span = { index -> GridItemSpan(tiles[index].spec.width()) }) { index
- ->
+ items(sizedTiles.size, span = { index -> GridItemSpan(sizedTiles[index].width) }) {
+ index ->
Tile(
- tile = tiles[index],
- iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec),
+ tile = sizedTiles[index].tile,
+ iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
)
}
@@ -77,13 +78,21 @@
onRemoveTile: (TileSpec) -> Unit,
) {
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
- val isIcon: (TileSpec) -> Boolean by rememberUpdatedState { tileSpec ->
- iconTilesViewModel.isIconTile(tileSpec)
- }
+ val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
+
+ // Non-current tiles should always be displayed as icon tiles.
+ val sizedTiles =
+ remember(tiles, largeTiles) {
+ tiles.map {
+ SizedTileImpl(
+ it,
+ if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1 else 2,
+ )
+ }
+ }
DefaultEditTileGrid(
- tiles = tiles,
- isIconOnly = isIcon,
+ sizedTiles = sizedTiles,
columns = columns,
modifier = modifier,
onAddTile = onAddTile,
@@ -99,7 +108,7 @@
): List<List<TileViewModel>> {
return PaginatableGridLayout.splitInRows(
- tiles.map { SizedTile(it, it.spec.width()) },
+ tiles.map { SizedTileImpl(it, it.spec.width()) },
columns,
)
.chunked(rows)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 9b4d10f..af3803b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -52,7 +52,7 @@
) { index ->
Tile(
tile = tiles[index],
- iconOnly = sizedTiles[index].width == 1,
+ iconOnly = sizedTiles[index].isIcon,
modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index cb9d0f6..7e6ccd6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -53,7 +53,6 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState
@@ -98,7 +97,8 @@
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.model.toTileGridCells
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -107,12 +107,10 @@
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.res.R
import java.util.function.Supplier
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
object TileType
-@OptIn(ExperimentalCoroutinesApi::class)
@Composable
fun Tile(
tile: TileViewModel,
@@ -286,15 +284,14 @@
@Composable
fun DefaultEditTileGrid(
- tiles: List<EditTileViewModel>,
- isIconOnly: (TileSpec) -> Boolean,
+ sizedTiles: List<SizedTile<EditTileViewModel>>,
columns: Int,
modifier: Modifier,
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
- onResize: (TileSpec, Boolean) -> Unit,
+ onResize: (TileSpec) -> Unit,
) {
- val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+ val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
val currentListState = rememberEditListState(currentTiles)
val dragAndDropState = rememberDragAndDropState(currentListState)
@@ -304,9 +301,6 @@
val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
onAddTile(tileSpec, position)
}
- val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
- onResize(tileSpec, !isIconOnly(tileSpec))
- }
val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
@@ -332,9 +326,8 @@
currentListState.tiles,
columns,
tilePadding,
- isIconOnly,
onRemoveTile,
- onDoubleTap,
+ onResize,
dragAndDropState,
onDropAdd,
)
@@ -422,48 +415,32 @@
@Composable
private fun CurrentTilesGrid(
- tiles: List<EditTileViewModel>,
+ tiles: List<SizedTile<EditTileViewModel>>,
columns: Int,
tilePadding: Dp,
- isIconOnly: (TileSpec) -> Boolean,
onClick: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
+ onResize: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState,
onDrop: (TileSpec, Int) -> Unit
) {
- val tileHeight = tileHeight()
- val currentRows =
- remember(tiles) {
- calculateRows(
- tiles.map {
- SizedTile(
- it,
- if (isIconOnly(it.tileSpec)) {
- 1
- } else {
- 2
- }
- )
- },
- columns
- )
- }
- val currentGridHeight = gridHeight(currentRows, tileHeight, tilePadding)
// Current tiles
CurrentTilesContainer {
+ val cells = tiles.toTileGridCells(columns)
+ val tileHeight = tileHeight()
+ val totalRows = cells.lastOrNull()?.row ?: 0
+ val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
TileLazyGrid(
modifier =
- Modifier.height(currentGridHeight)
+ Modifier.height(totalHeight)
.dragAndDropTileList(dragAndDropState, { true }, onDrop),
columns = GridCells.Fixed(columns)
) {
editTiles(
- tiles,
+ cells,
ClickAction.REMOVE,
onClick,
- isIconOnly,
dragAndDropState,
- onDoubleTap = onDoubleTap,
+ onResize = onResize,
indicatePosition = true,
acceptDrops = { true },
onDrop = onDrop,
@@ -474,13 +451,15 @@
@Composable
private fun AvailableTileGrid(
- tiles: List<EditTileViewModel>,
+ tiles: List<SizedTile<EditTileViewModel>>,
columns: Int,
tilePadding: Dp,
onClick: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState,
) {
- val (otherTilesStock, otherTilesCustom) = tiles.partition { it.appName == null }
+ // Available tiles aren't visible during drag and drop, so the row isn't needed
+ val (otherTilesStock, otherTilesCustom) =
+ tiles.map { TileGridCell(it, 0) }.partition { it.tile.appName == null }
val availableTileHeight = tileHeight(true)
val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
@@ -493,7 +472,6 @@
otherTilesStock,
ClickAction.ADD,
onClick,
- isIconOnly = { true },
dragAndDropState = dragAndDropState,
acceptDrops = { false },
showLabels = true,
@@ -502,7 +480,6 @@
otherTilesCustom,
ClickAction.ADD,
onClick,
- isIconOnly = { true },
dragAndDropState = dragAndDropState,
acceptDrops = { false },
showLabels = true,
@@ -519,52 +496,27 @@
return ((tileHeight + padding) * rows) - padding
}
-private fun calculateRows(tiles: List<SizedTile<EditTileViewModel>>, columns: Int): Int {
- val row = TileRow<EditTileViewModel>(columns)
- var count = 0
-
- for (tile in tiles) {
- if (row.maybeAddTile(tile)) {
- if (row.isFull()) {
- // Row is full, no need to stretch tiles
- count += 1
- row.clear()
- }
- } else {
- count += 1
- row.clear()
- row.maybeAddTile(tile)
- }
- }
- if (row.tiles.isNotEmpty()) {
- count += 1
- }
- return count
-}
-
fun LazyGridScope.editTiles(
- tiles: List<EditTileViewModel>,
+ cells: List<TileGridCell>,
clickAction: ClickAction,
onClick: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
dragAndDropState: DragAndDropState,
acceptDrops: (TileSpec) -> Boolean,
- onDoubleTap: (TileSpec) -> Unit = {},
+ onResize: (TileSpec) -> Unit = {},
onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
showLabels: Boolean = false,
indicatePosition: Boolean = false,
) {
items(
- count = tiles.size,
- key = { tiles[it].tileSpec.spec },
- span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+ count = cells.size,
+ key = { cells[it].key },
+ span = { cells[it].span },
contentType = { TileType }
) { index ->
- val viewModel = tiles[index]
- val iconOnly = isIconOnly(viewModel.tileSpec)
- val tileHeight = tileHeight(iconOnly && showLabels)
+ val cell = cells[index]
+ val tileHeight = tileHeight(cell.isIcon && showLabels)
- if (!dragAndDropState.isMoving(viewModel.tileSpec)) {
+ if (!dragAndDropState.isMoving(cell.tile.tileSpec)) {
val onClickActionName =
when (clickAction) {
ClickAction.ADD ->
@@ -579,8 +531,8 @@
""
}
EditTile(
- tileViewModel = viewModel,
- iconOnly = iconOnly,
+ tileViewModel = cell.tile,
+ iconOnly = cell.isIcon,
showLabels = showLabels,
modifier =
Modifier.height(tileHeight)
@@ -589,11 +541,11 @@
onClick(onClickActionName) { false }
this.stateDescription = stateDescription
}
- .dragAndDropTile(dragAndDropState, viewModel.tileSpec, acceptDrops, onDrop)
+ .dragAndDropTile(dragAndDropState, cell.tile.tileSpec, acceptDrops, onDrop)
.dragAndDropTileSource(
- viewModel,
+ cell,
onClick,
- onDoubleTap,
+ onResize,
dragAndDropState,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
new file mode 100644
index 0000000..c241fd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.panels.ui.model
+
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Immutable
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+
+/**
+ * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's
+ * positioned at
+ */
+@Immutable
+data class TileGridCell(
+ override val tile: EditTileViewModel,
+ val row: Int,
+ val key: String = "${tile.tileSpec.spec}-$row",
+ override val width: Int,
+) : SizedTile<EditTileViewModel> {
+ constructor(
+ sizedTile: SizedTile<EditTileViewModel>,
+ row: Int
+ ) : this(
+ tile = sizedTile.tile,
+ row = row,
+ width = sizedTile.width,
+ )
+
+ val span = GridItemSpan(width)
+}
+
+fun List<SizedTile<EditTileViewModel>>.toTileGridCells(columns: Int): List<TileGridCell> {
+ return splitInRowsSequence(this, columns)
+ .flatMapIndexed { index, sizedTiles -> sizedTiles.map { TileGridCell(it, index) } }
+ .toList()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 8d2d74a..b604e18 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -20,17 +20,22 @@
import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
interface IconTilesViewModel {
+ val largeTiles: StateFlow<Set<TileSpec>>
+
fun isIconTile(spec: TileSpec): Boolean
- fun resize(spec: TileSpec, toIcon: Boolean)
+ fun resize(spec: TileSpec)
}
@SysUISingleton
class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
IconTilesViewModel {
+ override val largeTiles = interactor.largeTilesSpecs
+
override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
- override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
+ override fun resize(spec: TileSpec) = interactor.resize(spec)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index bb00494..eee905f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -20,7 +20,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -59,7 +60,12 @@
.flatMapLatest { columns ->
tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
tiles
- .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .map {
+ SizedTileImpl(
+ TileViewModel(it.tile, it.spec),
+ it.spec.width,
+ )
+ }
.let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
}
}
@@ -67,7 +73,12 @@
applicationScope,
SharingStarted.WhileSubscribed(),
tilesInteractor.currentTiles.value
- .map { SizedTile(TileViewModel(it.tile, it.spec), it.spec.width) }
+ .map {
+ SizedTileImpl(
+ TileViewModel(it.tile, it.spec),
+ it.spec.width,
+ )
+ }
.let {
splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
}
@@ -75,26 +86,4 @@
private val TileSpec.width: Int
get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
-
- companion object {
- private fun splitInRowsSequence(
- tiles: List<SizedTile<TileViewModel>>,
- columns: Int,
- ): Sequence<List<SizedTile<TileViewModel>>> = sequence {
- val row = TileRow<TileViewModel>(columns)
- for (tile in tiles) {
- check(tile.width <= columns)
- if (!row.maybeAddTile(tile)) {
- // Couldn't add tile to previous row, create a row with the current tiles
- // and start a new one
- yield(row.tiles)
- row.clear()
- row.maybeAddTile(tile)
- }
- }
- if (row.tiles.isNotEmpty()) {
- yield(row.tiles)
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index abc0453..6a8cc17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -1082,7 +1082,12 @@
inner class StateChangeRunnable(private val state: QSTile.State) : Runnable {
override fun run() {
- traceSection("QSTileViewImpl#handleStateChanged") { handleStateChanged(state) }
+ var traceTag = "QSTileViewImpl#handleStateChanged"
+ if (!state.spec.isNullOrEmpty()) {
+ traceTag += ":"
+ traceTag += state.spec
+ }
+ traceSection(traceTag.take(Trace.MAX_SECTION_NAME_LEN)) { handleStateChanged(state) }
}
// We want all instances of this runnable to be equal to each other, so they can be used to
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index ddd0c76..9bcf927 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -34,7 +34,6 @@
import dagger.assisted.AssistedInject
import java.io.PrintWriter
import java.util.concurrent.CopyOnWriteArraySet
-import java.util.function.Supplier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectIndexed
@@ -158,6 +157,8 @@
override fun isTileReady(): Boolean = qsTileViewModel.currentState != null
+ private var cachedState = QSTile.AdapterState()
+
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
if (listening) {
@@ -168,7 +169,10 @@
.filterNotNull()
.map { mapState(context, it, qsTileViewModel.config) }
.onEach { legacyState ->
- callbacks.forEach { it.onStateChanged(legacyState) }
+ val changed = legacyState.copyTo(cachedState)
+ if (changed) {
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
}
.launchIn(applicationScope)
}
@@ -235,7 +239,7 @@
handlesLongClick =
viewModelState.supportedActions.contains(QSTileState.UserAction.LONG_CLICK)
- iconSupplier = Supplier {
+ icon =
when (val stateIcon = viewModelState.icon()) {
is Icon.Loaded ->
if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
@@ -243,7 +247,7 @@
is Icon.Resource -> ResourceIcon.get(stateIcon.res)
null -> null
}
- }
+
state = viewModelState.activationState.legacyState
contentDescription = viewModelState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index b1cc55d..7258882 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -34,7 +34,6 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -48,10 +47,9 @@
class QuickSettingsSceneViewModel
@Inject
constructor(
- val brightnessMirrorViewModel: BrightnessMirrorViewModel,
- val shadeHeaderViewModel: ShadeHeaderViewModel,
+ val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
+ val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
val qsSceneAdapter: QSSceneAdapter,
- val notifications: NotificationsPlaceholderViewModel,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
sceneBackInteractor: SceneBackInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
new file mode 100644
index 0000000..d2967b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.qs.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeAlignment
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models the UI state for the user actions that the user can perform to navigate to other scenes.
+ *
+ * Different from the [QuickSettingsShadeSceneContentViewModel] which models the _content_ of the
+ * scene.
+ */
+class QuickSettingsShadeSceneActionsViewModel
+@AssistedInject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ quickSettingsContainerViewModel.editModeViewModel.isEditing
+ .map { editing ->
+ buildMap {
+ put(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ },
+ UserActionResult(SceneFamilies.Home)
+ )
+ if (!editing) {
+ put(Back, UserActionResult(SceneFamilies.Home))
+ }
+ }
+ }
+ .collectLatest { actions -> setActions(actions) }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): QuickSettingsShadeSceneActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
new file mode 100644
index 0000000..abfca4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.qs.ui.viewmodel
+
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Models UI state used to render the content of the quick settings shade scene.
+ *
+ * Different from [QuickSettingsShadeSceneActionsViewModel], which only models user actions that can
+ * be performed to navigate to other scenes.
+ */
+class QuickSettingsShadeSceneContentViewModel
+@AssistedInject
+constructor(
+ val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
+ val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+) : SysUiViewModel() {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): QuickSettingsShadeSceneContentViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
deleted file mode 100644
index e012f2c..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.ui.viewmodel
-
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeAlignment
-import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Models UI state and handles user input for the Quick Settings Shade scene. */
-@SysUISingleton
-class QuickSettingsShadeSceneViewModel
-@Inject
-constructor(
- private val shadeInteractor: ShadeInteractor,
- val overlayShadeViewModel: OverlayShadeViewModel,
- val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) {
-
- val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- quickSettingsContainerViewModel.editModeViewModel.isEditing.map { editing ->
- buildMap {
- put(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- },
- UserActionResult(SceneFamilies.Home)
- )
- if (!editing) {
- put(Back, UserActionResult(SceneFamilies.Home))
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 8d0a386..432a35a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,7 +16,9 @@
package com.android.systemui.recents;
+import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
@@ -53,6 +55,8 @@
import androidx.annotation.NonNull;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.CoreStartable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -83,6 +87,7 @@
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final AccessibilityManager mAccessibilityService;
private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final UserTracker mUserTracker;
@@ -106,13 +111,16 @@
NavigationModeController navigationModeController,
Lazy<NavigationBarController> navigationBarControllerLazy,
BroadcastDispatcher broadcastDispatcher,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ Lazy<ViewCapture> daggerLazyViewCapture) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mAccessibilityService = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mWindowManager = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
+ mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
+ toKotlinLazy(daggerLazyViewCapture), enableViewCaptureTracing());
mNavBarMode = navigationModeController.addListener(this);
mBroadcastDispatcher = broadcastDispatcher;
mUserTracker = userTracker;
@@ -123,7 +131,7 @@
public void clearPrompt() {
if (mRequestWindow != null) {
- mWindowManager.removeView(mRequestWindow);
+ mViewCaptureAwareWindowManager.removeView(mRequestWindow);
mRequestWindow = null;
}
}
@@ -144,7 +152,7 @@
// show the confirmation
WindowManager.LayoutParams lp = getWindowLayoutParams();
- mWindowManager.addView(mRequestWindow, lp);
+ mViewCaptureAwareWindowManager.addView(mRequestWindow, lp);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
index 56270ce..a42bd0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceSettingsDialogDelegate.kt
@@ -81,9 +81,11 @@
}
setOnClickListener { showCategorySelector(this) }
}
+ val attachToBRLabel = context.getString(T.string.attach_to_bug_report)
requireViewById<Switch>(R.id.attach_to_bugreport_switch).apply {
isChecked = builder.attachToBugreport
setOnCheckedChangeListener { _, isChecked -> builder.attachToBugreport = isChecked }
+ contentDescription = attachToBRLabel
}
requireViewById<TextView>(R.id.cpu_buffer_size).setupSingleChoiceText(
T.array.buffer_size_values,
@@ -111,6 +113,7 @@
) {
builder.maxLongTraceDurationMinutes = it
}
+ val longTracesLabel = context.getString(T.string.long_traces)
requireViewById<Switch>(R.id.long_traces_switch).apply {
isChecked = builder.longTrace
val disabledAlpha by lazy { getDisabledAlpha(context) }
@@ -127,23 +130,24 @@
longTraceDurationText.alpha = newAlpha
longTraceSizeText.alpha = newAlpha
}
+ contentDescription = longTracesLabel
}
+ val winscopeLabel = context.getString(T.string.winscope_tracing)
requireViewById<Switch>(R.id.winscope_switch).apply {
isChecked = builder.winscope
setOnCheckedChangeListener { _, isChecked -> builder.winscope = isChecked }
+ contentDescription = winscopeLabel
}
+ val debuggableAppsLabel = context.getString(T.string.trace_debuggable_applications)
requireViewById<Switch>(R.id.trace_debuggable_apps_switch).apply {
isChecked = builder.apps
setOnCheckedChangeListener { _, isChecked -> builder.apps = isChecked }
+ contentDescription = debuggableAppsLabel
}
- requireViewById<TextView>(R.id.long_traces_switch_label).text =
- context.getString(T.string.long_traces)
- requireViewById<TextView>(R.id.debuggable_apps_switch_label).text =
- context.getString(T.string.trace_debuggable_applications)
- requireViewById<TextView>(R.id.winscope_switch_label).text =
- context.getString(T.string.winscope_tracing)
- requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text =
- context.getString(T.string.attach_to_bug_report)
+ requireViewById<TextView>(R.id.long_traces_switch_label).text = longTracesLabel
+ requireViewById<TextView>(R.id.debuggable_apps_switch_label).text = debuggableAppsLabel
+ requireViewById<TextView>(R.id.winscope_switch_label).text = winscopeLabel
+ requireViewById<TextView>(R.id.attach_to_bugreport_switch_label).text = attachToBRLabel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 98a61df..863a899 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -24,6 +24,7 @@
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
+import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.qualifiers.LongRunning
@@ -71,6 +72,7 @@
override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ Log.d(getTag(), "handling action: ${intent?.action}")
when (intent?.action) {
ACTION_START -> {
bgExecutor.execute {
@@ -95,7 +97,7 @@
bgExecutor.execute {
mNotificationManager.cancelAsUser(
null,
- mNotificationId,
+ intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId),
UserHandle(mUserContextTracker.userContext.userId)
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/OWNERS b/packages/SystemUI/src/com/android/systemui/scene/OWNERS
new file mode 100644
index 0000000..2ffcad4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+
+# Bug component: 1215786
+
+justinweir@google.com
+nijamkin@google.com
+
+# SysUI Dr No's.
+# Don't send reviews here.
+cinek@google.com
+dsandler@android.com
+juliacr@google.com
+pixel@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
new file mode 100644
index 0000000..b707a5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scene.ui.viewmodel
+
+import androidx.compose.ui.Alignment
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+
+class GoneSceneActionsViewModel
+@AssistedInject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ shadeInteractor.shadeMode
+ .map { shadeMode ->
+ buildMap<UserAction, UserActionResult> {
+ if (
+ shadeMode is ShadeMode.Single ||
+ // TODO(b/338577208): Remove this once we add Dual Shade invocation
+ // zones.
+ shadeMode is ShadeMode.Dual
+ ) {
+ if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Bottom,
+ direction = SwipeDirection.Up,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
+ )
+ } else {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Top,
+ direction = SwipeDirection.Down,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings)
+ )
+ }
+ }
+
+ if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
+ put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
+ } else {
+ put(
+ Swipe.Down,
+ UserActionResult(
+ SceneFamilies.NotifShade,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
+ )
+ }
+ }
+ }
+ .collectLatest { setActions(it) }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): GoneSceneActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
deleted file mode 100644
index b739ffe..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.scene.ui.viewmodel
-
-import androidx.compose.ui.Alignment
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-@SysUISingleton
-class GoneSceneViewModel
-@Inject
-constructor(
- private val shadeInteractor: ShadeInteractor,
-) {
- val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- shadeInteractor.shadeMode.map { shadeMode ->
- buildMap {
- if (
- shadeMode is ShadeMode.Single ||
- // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
- shadeMode is ShadeMode.Dual
- ) {
- if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Bottom,
- direction = SwipeDirection.Up,
- ),
- UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
- )
- } else {
- put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Top,
- direction = SwipeDirection.Down,
- ),
- UserActionResult(SceneFamilies.QuickSettings)
- )
- }
- }
-
- if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
- } else {
- put(
- Swipe.Down,
- UserActionResult(
- SceneFamilies.NotifShade,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
new file mode 100644
index 0000000..c2fd65b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.scene.ui.viewmodel
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.SysUiViewModel
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Base class for view-models that need to keep a map of scene actions (also known as "destination
+ * scenes") up-to-date.
+ *
+ * Subclasses need only to override [hydrateActions], suspending forever if they need; they don't
+ * need to worry about resetting the value of [actions] when the view-model is deactivated/canceled,
+ * this base class takes care of it.
+ */
+abstract class SceneActionsViewModel : SysUiViewModel() {
+
+ private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
+ /**
+ * [UserActionResult] by [UserAction] to be collected by the scene container to enable the right
+ * user input/gestures.
+ */
+ val actions: StateFlow<Map<UserAction, UserActionResult>> = _actions.asStateFlow()
+
+ final override suspend fun onActivated() {
+ try {
+ hydrateActions { state -> _actions.value = state }
+ awaitCancellation()
+ } finally {
+ _actions.value = emptyMap()
+ }
+ }
+
+ /**
+ * Keeps the user actions up-to-date (AKA "hydrated").
+ *
+ * Subclasses should implement this `suspend fun` by running coroutine work and calling
+ * [setActions] each time the actions should be published/updated. The work can safely suspend
+ * forever; the base class will take care of canceling it as needed. There's no need to handle
+ * cancellation in this method.
+ *
+ * The base class will also take care of resetting the [actions] flow back to the default value
+ * when this happens.
+ */
+ protected abstract suspend fun hydrateActions(
+ setActions: (Map<UserAction, UserActionResult>) -> Unit,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index cbb61b3..700253ba 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -63,7 +63,9 @@
protected static final int NOTIF_BASE_ID = 4273;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
- private static final String GROUP_KEY = "screen_record_saved";
+ @VisibleForTesting static final String GROUP_KEY_SAVED = "screen_record_saved";
+ private static final String GROUP_KEY_ERROR_STARTING = "screen_record_error_starting";
+ @VisibleForTesting static final String GROUP_KEY_ERROR_SAVING = "screen_record_error_saving";
private static final String EXTRA_RESULT_CODE = "extra_resultCode";
protected static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
@@ -78,6 +80,7 @@
"com.android.systemui.screenrecord.STOP_FROM_NOTIF";
protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+ protected static final String EXTRA_NOTIFICATION_ID = "notification_id";
private final RecordingController mController;
protected final KeyguardDismissUtil mKeyguardDismissUtil;
@@ -181,7 +184,7 @@
mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
} else {
updateState(false);
- createErrorNotification();
+ createErrorStartingNotification(currentUser);
stopForeground(STOP_FOREGROUND_DETACH);
stopSelf();
return Service.START_NOT_STICKY;
@@ -272,17 +275,35 @@
}
/**
- * Simple error notification, needed since startForeground must be called to avoid errors
+ * Simple "error starting" notification, needed since startForeground must be called to avoid
+ * errors.
*/
@VisibleForTesting
- protected void createErrorNotification() {
+ protected void createErrorStartingNotification(UserHandle currentUser) {
+ createErrorNotification(currentUser, strings().getStartError(), GROUP_KEY_ERROR_STARTING);
+ }
+
+ /**
+ * Simple "error saving" notification, needed since startForeground must be called to avoid
+ * errors.
+ */
+ @VisibleForTesting
+ protected void createErrorSavingNotification(UserHandle currentUser) {
+ createErrorNotification(currentUser, strings().getSaveError(), GROUP_KEY_ERROR_SAVING);
+ }
+
+ private void createErrorNotification(
+ UserHandle currentUser, String notificationContentTitle, String groupKey) {
+ // Make sure error notifications get their own group.
+ postGroupSummaryNotification(currentUser, notificationContentTitle, groupKey);
+
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
- String notificationTitle = strings().getStartError();
Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setContentTitle(notificationTitle)
+ .setContentTitle(notificationContentTitle)
+ .setGroup(groupKey)
.addExtras(extras);
startForeground(mNotificationId, builder.build());
}
@@ -337,7 +358,7 @@
.setContentText(
strings().getBackgroundProcessingLabel())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setGroup(GROUP_KEY)
+ .setGroup(GROUP_KEY_SAVED)
.addExtras(extras);
return builder.build();
}
@@ -374,7 +395,7 @@
PendingIntent.FLAG_IMMUTABLE))
.addAction(shareAction)
.setAutoCancel(true)
- .setGroup(GROUP_KEY)
+ .setGroup(GROUP_KEY_SAVED)
.addExtras(extras);
// Add thumbnail if available
@@ -389,21 +410,28 @@
}
/**
- * Adds a group notification so that save notifications from multiple recordings are
- * grouped together, and the foreground service recording notification is not
+ * Adds a group summary notification for save notifications so that save notifications from
+ * multiple recordings are grouped together, and the foreground service recording notification
+ * is not.
*/
- private void postGroupNotification(UserHandle currentUser) {
+ private void postGroupSummaryNotificationForSaves(UserHandle currentUser) {
+ postGroupSummaryNotification(currentUser, strings().getSaveTitle(), GROUP_KEY_SAVED);
+ }
+
+ /** Posts a group summary notification for the given group. */
+ private void postGroupSummaryNotification(
+ UserHandle currentUser, String notificationContentTitle, String groupKey) {
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
strings().getTitle());
Notification groupNotif = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setContentTitle(strings().getSaveTitle())
- .setGroup(GROUP_KEY)
+ .setContentTitle(notificationContentTitle)
+ .setGroup(groupKey)
.setGroupSummary(true)
.setExtras(extras)
.build();
- mNotificationManager.notifyAsUser(getTag(), NOTIF_BASE_ID, groupNotif, currentUser);
+ mNotificationManager.notifyAsUser(getTag(), mNotificationId, groupNotif, currentUser);
}
private void stopService() {
@@ -414,6 +442,7 @@
if (userId == USER_ID_NOT_SPECIFIED) {
userId = mUserContextTracker.getUserContext().getUserId();
}
+ UserHandle currentUser = new UserHandle(userId);
Log.d(getTag(), "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
try {
@@ -427,11 +456,11 @@
// let's release the recorder and delete all temporary files in this case
getRecorder().release();
}
- showErrorToast(R.string.screenrecord_start_error);
+ showErrorToast(R.string.screenrecord_save_error);
Log.e(getTag(), "stopRecording called, but there was an error when ending"
+ "recording");
exception.printStackTrace();
- createErrorNotification();
+ createErrorSavingNotification(currentUser);
} catch (Throwable throwable) {
if (getRecorder() != null) {
// Something unexpected happen, SystemUI will crash but let's delete
@@ -455,7 +484,7 @@
Log.d(getTag(), "saving recording");
Notification notification = createSaveNotification(
getRecorder() != null ? getRecorder().save() : null);
- postGroupNotification(currentUser);
+ postGroupSummaryNotificationForSaves(currentUser);
mNotificationManager.notifyAsUser(null, mNotificationId, notification,
currentUser);
} catch (IOException | IllegalStateException e) {
@@ -514,7 +543,8 @@
private Intent getShareIntent(Context context, Uri path) {
return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
- .putExtra(EXTRA_PATH, path);
+ .putExtra(EXTRA_PATH, path)
+ .putExtra(EXTRA_NOTIFICATION_ID, mNotificationId);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 540d4c4..7b802a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -17,7 +17,6 @@
package com.android.systemui.screenshot;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.screenshotSaveImageExporter;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
@@ -31,7 +30,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -52,17 +50,12 @@
import android.util.Log;
import android.view.Display;
import android.view.ScrollCaptureResponse;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.WindowContext;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
@@ -115,11 +108,9 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final ScreenshotActionsController mActionsController;
- private final WindowManager mWindowManager;
- private final WindowManager.LayoutParams mWindowLayoutParams;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
- private final PhoneWindow mWindow;
+ private final ScreenshotWindow mWindow;
private final Display mDisplay;
private final ScrollCaptureExecutor mScrollCaptureExecutor;
private final ScreenshotNotificationSmartActionsProvider
@@ -135,8 +126,6 @@
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
- private boolean mAttachRequested;
- private boolean mDetachRequested;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
private String mPackageName = "";
@@ -155,7 +144,7 @@
@AssistedInject
ScreenshotController(
Context context,
- WindowManager windowManager,
+ ScreenshotWindow.Factory screenshotWindowFactory,
FeatureFlags flags,
ScreenshotShelfViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
@@ -195,9 +184,8 @@
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mDisplay = display;
- mWindowManager = windowManager;
- final Context displayContext = context.createDisplayContext(display);
- mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+ mWindow = screenshotWindowFactory.create(mDisplay);
+ mContext = mWindow.getContext();
mFlags = flags;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
@@ -213,17 +201,10 @@
mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
});
- // Setup the window that we are going to use
- mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
- mWindowLayoutParams.setTitle("ScreenshotAnimation");
-
- mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
- mWindow.setWindowManager(mWindowManager, null, null);
-
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
- mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
+ mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy,
() -> {
finishDismiss();
return Unit.INSTANCE;
@@ -318,12 +299,12 @@
}
// The window is focusable by default
- setWindowFocusable(true);
+ mWindow.setFocusable(true);
mViewProxy.requestFocus();
enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
- attachWindow();
+ mWindow.attachWindow();
boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
@@ -347,13 +328,10 @@
mViewProxy.setScreenshot(screenshot);
- // ignore system bar insets for the purpose of window layout
- mWindow.getDecorView().setOnApplyWindowInsetsListener(
- (v, insets) -> WindowInsets.CONSUMED);
}
void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
- withWindowAttached(() -> {
+ mWindow.whenWindowAttached(() -> {
mAnnouncementResolver.getScreenshotAnnouncement(
screenshot.getUserHandle().getIdentifier(),
announcement -> {
@@ -444,7 +422,7 @@
@Override
public void onTouchOutside() {
// TODO(159460485): Remove this when focus is handled properly in the system
- setWindowFocusable(false);
+ mWindow.setFocusable(false);
}
});
@@ -457,9 +435,9 @@
private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
- withWindowAttached(() -> {
+ mWindow.whenWindowAttached(() -> {
requestScrollCapture(requestId, owner);
- mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ mWindow.setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
public void onConfigurationChanged(Configuration overrideConfig,
@@ -472,8 +450,7 @@
// to set up in the new orientation.
mScreenshotHandler.postDelayed(
() -> requestScrollCapture(requestId, owner), 150);
- mViewProxy.updateInsets(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ mViewProxy.updateInsets(mWindow.getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
if (mScreenshotAnimation != null
@@ -489,7 +466,7 @@
private void requestScrollCapture(UUID requestId, UserHandle owner) {
mScrollCaptureExecutor.requestScrollCapture(
mDisplay.getDisplayId(),
- mWindow.getDecorView().getWindowToken(),
+ mWindow.getWindowToken(),
(response) -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
0, response.getPackageName());
@@ -528,61 +505,9 @@
mViewProxy::startLongScreenshotTransition);
}
- private void withWindowAttached(Runnable action) {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
- action.run();
- } else {
- decorView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- mAttachRequested = false;
- decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
- action.run();
- }
-
- @Override
- public void onWindowDetached() {
- }
- });
-
- }
- }
-
- @MainThread
- private void attachWindow() {
- View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow() || mAttachRequested) {
- return;
- }
- if (DEBUG_WINDOW) {
- Log.d(TAG, "attachWindow");
- }
- mAttachRequested = true;
- mWindowManager.addView(decorView, mWindowLayoutParams);
- decorView.requestApplyInsets();
-
- ViewGroup layout = decorView.requireViewById(android.R.id.content);
- layout.setClipChildren(false);
- layout.setClipToPadding(false);
- }
-
@Override
public void removeWindow() {
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "Removing screenshot window");
- }
- mWindowManager.removeViewImmediate(decorView);
- mDetachRequested = false;
- }
- if (mAttachRequested && !mDetachRequested) {
- mDetachRequested = true;
- withWindowAttached(this::removeWindow);
- }
-
+ mWindow.removeWindow();
mViewProxy.stopInputListening();
}
@@ -759,33 +684,6 @@
.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
- /**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setWindowFocusable: " + focusable);
- }
- int flags = mWindowLayoutParams.flags;
- if (focusable) {
- mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
- if (mWindowLayoutParams.flags == flags) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
- }
- return;
- }
- final View decorView = mWindow.peekDecorView();
- if (decorView != null && decorView.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
- }
- }
-
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(displayMetrics);
@@ -826,6 +724,6 @@
*
* @param display display to capture
*/
- LegacyScreenshotController create(Display display);
+ ScreenshotController create(Display display);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
new file mode 100644
index 0000000..644e12c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.R
+import android.annotation.MainThread
+import android.content.Context
+import android.graphics.PixelFormat
+import android.os.IBinder
+import android.util.Log
+import android.view.Display
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewRootImpl
+import android.view.ViewTreeObserver.OnWindowAttachListener
+import android.view.Window
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.window.WindowContext
+import com.android.internal.policy.PhoneWindow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Creates and manages the window in which the screenshot UI is displayed. */
+class ScreenshotWindow
+@AssistedInject
+constructor(
+ private val windowManager: WindowManager,
+ private val context: Context,
+ @Assisted private val display: Display,
+) {
+
+ val window: PhoneWindow =
+ PhoneWindow(
+ context
+ .createDisplayContext(display)
+ .createWindowContext(WindowManager.LayoutParams.TYPE_SCREENSHOT, null)
+ )
+ private val params =
+ WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 0, /* xpos */
+ 0, /* ypos */
+ WindowManager.LayoutParams.TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN or
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ PixelFormat.TRANSLUCENT
+ )
+ .apply {
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ setFitInsetsTypes(0)
+ // This is needed to let touches pass through outside the touchable areas
+ privateFlags =
+ privateFlags or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+ title = "ScreenshotUI"
+ }
+ private var attachRequested: Boolean = false
+ private var detachRequested: Boolean = false
+
+ init {
+ window.requestFeature(Window.FEATURE_NO_TITLE)
+ window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+ window.setBackgroundDrawableResource(R.color.transparent)
+ window.setWindowManager(windowManager, null, null)
+ }
+
+ @MainThread
+ fun attachWindow() {
+ val decorView: View = window.getDecorView()
+ if (decorView.isAttachedToWindow || attachRequested) {
+ return
+ }
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "attachWindow")
+ }
+ attachRequested = true
+ windowManager.addView(decorView, params)
+
+ decorView.requestApplyInsets()
+ decorView.requireViewById<ViewGroup>(R.id.content).apply {
+ clipChildren = false
+ clipToPadding = false
+ // ignore system bar insets for the purpose of window layout
+ setOnApplyWindowInsetsListener { _, _ -> WindowInsets.CONSUMED }
+ }
+ }
+
+ fun whenWindowAttached(action: Runnable) {
+ val decorView: View = window.getDecorView()
+ if (decorView.isAttachedToWindow) {
+ action.run()
+ } else {
+ decorView
+ .getViewTreeObserver()
+ .addOnWindowAttachListener(
+ object : OnWindowAttachListener {
+ override fun onWindowAttached() {
+ attachRequested = false
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this)
+ action.run()
+ }
+
+ override fun onWindowDetached() {}
+ }
+ )
+ }
+ }
+
+ fun removeWindow() {
+ val decorView: View? = window.peekDecorView()
+ if (decorView != null && decorView.isAttachedToWindow) {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "Removing screenshot window")
+ }
+ windowManager.removeViewImmediate(decorView)
+ detachRequested = false
+ }
+ if (attachRequested && !detachRequested) {
+ detachRequested = true
+ whenWindowAttached { removeWindow() }
+ }
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the window
+ * immediately, otherwise the layout params will be applied when the window is next shown.
+ */
+ fun setFocusable(focusable: Boolean) {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: $focusable")
+ }
+ val flags: Int = params.flags
+ if (focusable) {
+ params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
+ } else {
+ params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ }
+ if (params.flags == flags) {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: skipping, already $focusable")
+ }
+ return
+ }
+ window.peekDecorView()?.also {
+ if (it.isAttachedToWindow) {
+ windowManager.updateViewLayout(it, params)
+ }
+ }
+ }
+
+ fun getContext(): WindowContext = window.context as WindowContext
+
+ fun getWindowToken(): IBinder = window.decorView.windowToken
+
+ fun getWindowInsets(): WindowInsets = windowManager.currentWindowMetrics.windowInsets
+
+ fun setContentView(view: View) {
+ window.setContentView(view)
+ }
+
+ fun setActivityConfigCallback(callback: ViewRootImpl.ActivityConfigCallback) {
+ window.peekDecorView().viewRootImpl.setActivityConfigCallback(callback)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(display: Display): ScreenshotWindow
+ }
+
+ companion object {
+ private const val TAG = "ScreenshotWindow"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
index 79e8b87..7f8c146 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -19,25 +19,25 @@
import android.content.res.Resources
import android.util.Log
import android.view.View
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.BrightnessSliderController
import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.settings.brightness.ToggleSlider
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
-import javax.inject.Inject
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-@SysUISingleton
class BrightnessMirrorViewModel
-@Inject
+@AssistedInject
constructor(
private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
@Main private val resources: Resources,
val sliderControllerFactory: BrightnessSliderController.Factory,
-) : MirrorController {
+) : SysUiViewModel(), MirrorController {
private val tempPosition = IntArray(2)
@@ -99,6 +99,11 @@
override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
+ @AssistedFactory
+ interface Factory {
+ fun create(): BrightnessMirrorViewModel
+ }
+
companion object {
private const val TAG = "BrightnessMirrorViewModel"
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 05c50fe..15bbef0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -293,7 +293,7 @@
)
containerView.systemGestureExclusionRects =
- if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ if (Flags.hubmodeFullscreenVerticalSwipeFix()) {
listOf(
// Disable back gestures on the left side of the screen, to avoid
// conflicting with scene transitions.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 257390f..104d4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1097,14 +1097,6 @@
}
@Override
- public void onPulseExpansionAmountChanged(boolean expandingChanged) {
- if (mKeyguardBypassController.getBypassEnabled()) {
- // Position the notifications while dragging down while pulsing
- requestScrollerTopPaddingUpdate(false /* animate */);
- }
- }
-
- @Override
public void onDelayedDozeAmountAnimationRunning(boolean running) {
// On running OR finished, the animation is no longer waiting to play
setWillPlayDelayedDozeAmountAnimation(false);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 8b88da1..348b6ba 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,7 +27,6 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.lifecycle.lifecycleScope
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.fragments.FragmentService
@@ -191,11 +190,7 @@
}
private fun calculateLargeShadeHeaderHeight(): Int {
- return if (centralizedStatusBarHeightFix()) {
- largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
- } else {
- resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
- }
+ return largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
}
private fun calculateShadeHeaderHeight(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 9f61d4e..16aef65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -19,7 +19,6 @@
import static android.view.WindowInsets.Type.ime;
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
@@ -444,10 +443,7 @@
mUseLargeScreenShadeHeader =
LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources());
mLargeScreenShadeHeaderHeight =
- centralizedStatusBarHeightFix()
- ? mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
- : mResources.getDimensionPixelSize(
- R.dimen.large_screen_shade_header_height);
+ mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight();
int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
mShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
@@ -2256,8 +2252,11 @@
// panel, mQs will not need to be null cause it will be tied to the same lifecycle.
if (fragment == mQs) {
// Clear it to remove bindings to mQs from the provider.
- mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null);
- mNotificationStackScrollLayoutController.setQsHeader(null);
+ if (QSComposeFragment.isEnabled()) {
+ mNotificationStackScrollLayoutController.setQsHeaderBoundsProvider(null);
+ } else {
+ mNotificationStackScrollLayoutController.setQsHeader(null);
+ }
mQs = null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 37da114..c49cfbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -38,7 +38,6 @@
import com.android.app.animation.Interpolators
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -231,10 +230,12 @@
private val demoModeReceiver =
object : DemoMode {
override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+
override fun dispatchDemoCommand(command: String, args: Bundle) =
clock.dispatchDemoCommand(command, args)
override fun onDemoModeStarted() = clock.onDemoModeStarted()
+
override fun onDemoModeFinished() = clock.onDemoModeFinished()
}
@@ -442,9 +443,7 @@
changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
}
- if (centralizedStatusBarHeightFix()) {
- view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
- }
+ view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom)
view.updateAllConstraints(changes)
updateBatteryMode()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 684a484..d64b21f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -55,7 +55,7 @@
keyguardRepository: KeyguardRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
powerInteractor: PowerInteractor,
- shadeRepository: ShadeRepository,
+ private val shadeRepository: ShadeRepository,
userSetupRepository: UserSetupRepository,
userSwitcherInteractor: UserSwitcherInteractor,
private val baseShadeInteractor: BaseShadeInteractor,
@@ -114,11 +114,13 @@
initialValue = determineShadeMode(isShadeLayoutWide.value)
)
- override val shadeAlignment: ShadeAlignment =
- if (shadeRepository.isDualShadeAlignedToBottom) {
- ShadeAlignment.Bottom
- } else {
- ShadeAlignment.Top
+ override val shadeAlignment: ShadeAlignment
+ get() {
+ return if (shadeRepository.isDualShadeAlignedToBottom) {
+ ShadeAlignment.Bottom
+ } else {
+ ShadeAlignment.Top
+ }
}
override val isExpandToQsEnabled: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 9e221d3..f48e31e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import javax.inject.Inject
@@ -46,6 +47,10 @@
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
repository: ShadeRepository,
) : BaseShadeInteractor {
+ init {
+ SceneContainerFlag.assertInLegacyMode()
+ }
+
/**
* The amount [0-1] that the shade has been opened. Uses stateIn to avoid redundant calculations
* in downstream flows.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 9617b54..6a21531 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -22,8 +22,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -43,8 +44,12 @@
constructor(
@Application scope: CoroutineScope,
sceneInteractor: SceneInteractor,
- sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
+ shadeRepository: ShadeRepository,
) : BaseShadeInteractor {
+ init {
+ SceneContainerFlag.assertInNewMode()
+ }
+
override val shadeExpansion: StateFlow<Float> =
sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
.traceAsCounter("panel_expansion") { (it * 100f).toInt() }
@@ -55,7 +60,7 @@
override val qsExpansion: StateFlow<Float> =
combine(
- sharedNotificationContainerInteractor.isSplitShadeEnabled,
+ shadeRepository.isShadeLayoutWide,
shadeExpansion,
sceneBasedQsExpansion,
) { isSplitShadeEnabled, shadeExpansion, qsExpansion ->
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt
new file mode 100644
index 0000000..068d6a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/BaseShadeSceneViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+
+/** Base class for classes that model UI state of the content of shade scenes. */
+abstract class BaseShadeSceneViewModel(
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val sceneInteractor: SceneInteractor,
+) : SysUiViewModel() {
+
+ private val _isEmptySpaceClickable =
+ MutableStateFlow(!deviceEntryInteractor.isDeviceEntered.value)
+ /** Whether clicking on the empty area of the shade does something */
+ val isEmptySpaceClickable: StateFlow<Boolean> = _isEmptySpaceClickable.asStateFlow()
+
+ override suspend fun onActivated() {
+ deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
+ _isEmptySpaceClickable.value = !isDeviceEntered
+ }
+ }
+
+ /** Notifies that the empty space in the shade has been clicked. */
+ fun onEmptySpaceClicked() {
+ if (!isEmptySpaceClickable.value) {
+ return
+ }
+
+ sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty space clicked.")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
index e1289af..2f98488 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModel.kt
@@ -17,8 +17,11 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -29,7 +32,11 @@
@Inject
constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardInteractor: KeyguardInteractor,
) {
val isKeyguardOccluded: Flow<Boolean> =
- keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f }
+ anyOf(
+ keyguardTransitionInteractor.transitionValue(OCCLUDED).map { it == 1f },
+ keyguardTransitionInteractor.transitionValue(DREAMING).map { it == 1f },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 6551854..566bc16 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -17,43 +17,39 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
/**
* Models UI state and handles user input for the overlay shade UI, which shows a shade as an
* overlay on top of another scene UI.
*/
-@SysUISingleton
class OverlayShadeViewModel
-@Inject
-constructor(
- @Application applicationScope: CoroutineScope,
- private val sceneInteractor: SceneInteractor,
- shadeInteractor: ShadeInteractor
-) {
+@AssistedInject
+constructor(private val sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor) :
+ SysUiViewModel() {
+ private val _backgroundScene = MutableStateFlow(Scenes.Lockscreen)
/** The scene to show in the background when the overlay shade is open. */
- val backgroundScene: StateFlow<SceneKey> =
- sceneInteractor
- .resolveSceneFamily(SceneFamilies.Home)
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = Scenes.Lockscreen,
- )
+ val backgroundScene: StateFlow<SceneKey> = _backgroundScene.asStateFlow()
/** Dictates the alignment of the overlay shade panel on the screen. */
val panelAlignment = shadeInteractor.shadeAlignment
+ override suspend fun onActivated() {
+ sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collectLatest { sceneKey ->
+ _backgroundScene.value = sceneKey
+ }
+ }
+
/** Notifies that the user has clicked the semi-transparent background scrim. */
fun onScrimClicked() {
sceneInteractor.changeScene(
@@ -61,4 +57,9 @@
loggingReason = "Shade scrim clicked",
)
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): OverlayShadeViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index b2e0cd0..03fdfa9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -24,8 +24,7 @@
import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
@@ -38,44 +37,40 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import java.util.Date
import java.util.Locale
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Models UI state for the shade header. */
-@SysUISingleton
class ShadeHeaderViewModel
-@Inject
+@AssistedInject
constructor(
- @Application private val applicationScope: CoroutineScope,
- context: Context,
+ private val context: Context,
private val activityStarter: ActivityStarter,
private val sceneInteractor: SceneInteractor,
- shadeInteractor: ShadeInteractor,
- mobileIconsInteractor: MobileIconsInteractor,
+ private val shadeInteractor: ShadeInteractor,
+ private val mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
- broadcastDispatcher: BroadcastDispatcher,
-) {
+ private val broadcastDispatcher: BroadcastDispatcher,
+) : SysUiViewModel() {
/** True if there is exactly one mobile connection. */
val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier
+ private val _mobileSubIds = MutableStateFlow(emptyList<Int>())
/** The list of subscription Ids for current mobile connections. */
- val mobileSubIds =
- mobileIconsInteractor.filteredSubscriptions
- .map { list -> list.map { it.subscriptionId } }
- .stateIn(applicationScope, SharingStarted.WhileSubscribed(), emptyList())
+ val mobileSubIds: StateFlow<List<Int>> = _mobileSubIds.asStateFlow()
/** The list of PrivacyItems to be displayed by the privacy chip. */
val privacyItems: StateFlow<List<PrivacyItem>> = privacyChipInteractor.privacyItems
@@ -94,11 +89,9 @@
/** Whether or not the privacy chip is enabled in the device privacy config. */
val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
+ private val _isDisabled = MutableStateFlow(false)
/** Whether or not the Shade Header should be disabled based on disableFlags. */
- val isDisabled: StateFlow<Boolean> =
- shadeInteractor.isQsEnabled
- .map { !it }
- .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+ val isDisabled: StateFlow<Boolean> = _isDisabled.asStateFlow()
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
@@ -111,26 +104,40 @@
private val _longerDateText: MutableStateFlow<String> = MutableStateFlow("")
val longerDateText: StateFlow<String> = _longerDateText.asStateFlow()
- init {
- broadcastDispatcher
- .broadcastFlow(
- filter =
- IntentFilter().apply {
- addAction(Intent.ACTION_TIME_TICK)
- addAction(Intent.ACTION_TIME_CHANGED)
- addAction(Intent.ACTION_TIMEZONE_CHANGED)
- addAction(Intent.ACTION_LOCALE_CHANGED)
- },
- user = UserHandle.SYSTEM,
- map = { intent, _ ->
- intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
- intent.action == Intent.ACTION_LOCALE_CHANGED
- }
- )
- .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
- .launchIn(applicationScope)
+ override suspend fun onActivated() {
+ coroutineScope {
+ launch {
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ addAction(Intent.ACTION_TIMEZONE_CHANGED)
+ addAction(Intent.ACTION_LOCALE_CHANGED)
+ },
+ user = UserHandle.SYSTEM,
+ map = { intent, _ ->
+ intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
+ intent.action == Intent.ACTION_LOCALE_CHANGED
+ }
+ )
+ .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
+ .launchIn(this)
+ }
- applicationScope.launch { updateDateTexts(false) }
+ launch { updateDateTexts(false) }
+
+ launch {
+ mobileIconsInteractor.filteredSubscriptions
+ .map { list -> list.map { it.subscriptionId } }
+ .collectLatest { _mobileSubIds.value = it }
+ }
+
+ launch {
+ shadeInteractor.isQsEnabled.map { !it }.collectLatest { _isDisabled.value = it }
+ }
+ }
}
/** Notifies that the privacy chip was clicked. */
@@ -182,4 +189,9 @@
format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE)
return format
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): ShadeHeaderViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
new file mode 100644
index 0000000..bdc0fdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Models the UI state for the user actions that the user can perform to navigate to other scenes.
+ *
+ * Different from the [ShadeSceneContentViewModel] which models the _content_ of the scene.
+ */
+class ShadeSceneActionsViewModel
+@AssistedInject
+constructor(
+ private val qsSceneAdapter: QSSceneAdapter,
+ private val shadeInteractor: ShadeInteractor,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ combine(
+ shadeInteractor.shadeMode,
+ qsSceneAdapter.isCustomizerShowing,
+ ) { shadeMode, isCustomizerShowing ->
+ buildMap<UserAction, UserActionResult> {
+ if (!isCustomizerShowing) {
+ set(
+ Swipe(SwipeDirection.Up),
+ UserActionResult(
+ SceneFamilies.Home,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
+ )
+ }
+
+ // TODO(b/330200163) Add an else to be able to collapse the shade while
+ // customizing
+ if (shadeMode is ShadeMode.Single) {
+ set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+ }
+ }
+ }
+ .collectLatest { actions -> setActions(actions) }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): ShadeSceneActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
new file mode 100644
index 0000000..3cdff96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.ui.viewmodel
+
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * Models UI state used to render the content of the shade scene.
+ *
+ * Different from [ShadeSceneActionsViewModel], which only models user actions that can be performed
+ * to navigate to other scenes.
+ */
+class ShadeSceneContentViewModel
+@AssistedInject
+constructor(
+ val qsSceneAdapter: QSSceneAdapter,
+ val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
+ val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
+ val mediaCarouselInteractor: MediaCarouselInteractor,
+ shadeInteractor: ShadeInteractor,
+ private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+ private val footerActionsController: FooterActionsController,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
+ sceneInteractor: SceneInteractor,
+) :
+ BaseShadeSceneViewModel(
+ deviceEntryInteractor,
+ sceneInteractor,
+ ) {
+
+ val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+
+ private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+ /**
+ * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
+ * slightly, in pixels.
+ */
+ fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> {
+ return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide)
+ }
+
+ fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+ if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+ footerActionsController.init()
+ }
+ return footerActionsViewModelFactory.create(lifecycleOwner)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): ShadeSceneContentViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
deleted file mode 100644
index 06298ef..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ /dev/null
@@ -1,152 +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.shade.ui.viewmodel
-
-import androidx.lifecycle.LifecycleOwner
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.lifecycle.Activatable
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import com.android.systemui.qs.FooterActionsController
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.ui.adapter.QSSceneAdapter
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
-import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
-import java.util.concurrent.atomic.AtomicBoolean
-import javax.inject.Inject
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-/** Models UI state and handles user input for the shade scene. */
-@SysUISingleton
-class ShadeSceneViewModel
-@Inject
-constructor(
- val qsSceneAdapter: QSSceneAdapter,
- val shadeHeaderViewModel: ShadeHeaderViewModel,
- val brightnessMirrorViewModel: BrightnessMirrorViewModel,
- val mediaCarouselInteractor: MediaCarouselInteractor,
- shadeInteractor: ShadeInteractor,
- private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
- private val footerActionsController: FooterActionsController,
- private val sceneInteractor: SceneInteractor,
- private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-) : Activatable {
- val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- combine(
- shadeInteractor.shadeMode,
- qsSceneAdapter.isCustomizerShowing,
- ) { shadeMode, isCustomizerShowing ->
- buildMap {
- if (!isCustomizerShowing) {
- set(
- Swipe(SwipeDirection.Up),
- UserActionResult(
- SceneFamilies.Home,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- }
-
- // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
- if (shadeMode is ShadeMode.Single) {
- set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
- }
- }
- }
-
- private val upDestinationSceneKey: Flow<SceneKey?> =
- destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
-
- private val _isClickable = MutableStateFlow(false)
- /** Whether or not the shade container should be clickable. */
- val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow()
-
- /**
- * Activates the view-model.
- *
- * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to
- * keep its state fresh and/or perform side-effects.
- *
- * Suspends the caller forever as it will keep doing work until canceled.
- *
- * **Must be invoked** when the scene becomes the current scene or when it becomes visible
- * during a transition (the choice is the responsibility of the parent). Similarly, the work
- * must be canceled when the scene stops being visible or the current scene.
- */
- override suspend fun activate() {
- coroutineScope {
- launch {
- upDestinationSceneKey
- .flatMapLatestConflated { key ->
- key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
- }
- .map { it == Scenes.Lockscreen }
- .collectLatest { _isClickable.value = it }
- }
- }
- }
-
- val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
-
- val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
-
- /**
- * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
- * slightly, in pixels.
- */
- fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> {
- return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide)
- }
-
- /** Notifies that some content in the shade was clicked. */
- fun onContentClicked() {
- if (!isClickable.value) {
- return
- }
-
- sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked")
- }
-
- private val footerActionsControllerInitialized = AtomicBoolean(false)
-
- fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
- if (footerActionsControllerInitialized.compareAndSet(false, true)) {
- footerActionsController.init()
- }
- return footerActionsViewModelFactory.create(lifecycleOwner)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index cea97d6..50be6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -19,7 +19,6 @@
import static android.app.StatusBarManager.DISABLE2_NONE;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
@@ -1219,7 +1218,7 @@
&& mLastUpdatedImeDisplayId != INVALID_DISPLAY) {
// Set previous NavBar's IME window status as invisible when IME
// window switched to another display for single-session IME case.
- sendImeInvisibleStatusForPrevNavBar();
+ sendImeNotVisibleStatusForPrevNavBar();
}
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher);
@@ -1227,9 +1226,9 @@
mLastUpdatedImeDisplayId = displayId;
}
- private void sendImeInvisibleStatusForPrevNavBar() {
+ private void sendImeNotVisibleStatusForPrevNavBar() {
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, IME_INVISIBLE,
+ mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, 0 /* vis */,
BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index c1eb8bc..5eef8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -785,7 +785,8 @@
private void updateLockScreenAdaptiveAuthMsg(int userId) {
final boolean deviceLocked = mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(userId);
- if (deviceLocked) {
+ final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(userId);
+ if (deviceLocked && !canSkipBouncer) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_ADAPTIVE_AUTH,
new KeyguardIndication.Builder()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 49743bf..ccea254 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.util.time.SystemClock;
@@ -66,7 +65,6 @@
private final SystemClock mSystemClock;
private final Executor mMainExecutor;
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
- private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
private final Deque<RankingMap> mRankingMapQueue = new ConcurrentLinkedDeque<>();
private final Runnable mDispatchRankingUpdateRunnable = this::dispatchRankingUpdate;
@@ -99,13 +97,6 @@
mNotificationHandlers.add(handler);
}
- /** Registers a listener that's notified when any notification-related settings change. */
- @Deprecated
- public void addNotificationSettingsListener(NotificationSettingsListener listener) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mSettingsListeners.add(listener);
- }
-
@Override
public void onListenerConnected() {
if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -237,13 +228,7 @@
@Override
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
- if (NotificationIconContainerRefactor.isEnabled()) {
- mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
- } else {
- for (NotificationSettingsListener listener : mSettingsListeners) {
- listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
- }
- }
+ mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
}
public final void unsnoozeNotification(@NonNull String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 28e3a83..696e222 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -48,7 +48,6 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -153,11 +152,7 @@
R.dimen.notification_corner_animation_distance);
mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
- if (NotificationIconContainerRefactor.isEnabled()) {
- mShelfIcons.setOverrideIconColor(true);
- } else {
- mShelfIcons.setInNotificationIconShelf(true);
- }
+ mShelfIcons.setOverrideIconColor(true);
if (!mShowNotificationShelf) {
setVisibility(GONE);
}
@@ -228,9 +223,6 @@
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
}
- if (!NotificationIconContainerRefactor.isEnabled()) {
- viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
- }
viewState.hideSensitive = false;
viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -276,30 +268,7 @@
}
}
- private int getSpeedBumpIndex() {
- NotificationIconContainerRefactor.assertInLegacyMode();
- return mHostLayout.getSpeedBumpIndex();
- }
-
- /**
- * @param fractionToShade Fraction of lockscreen to shade transition
- * @param shortestWidth Shortest width to use for lockscreen shelf
- */
- @VisibleForTesting
- public void updateActualWidth(float fractionToShade, float shortestWidth) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- final float actualWidth = mAmbientState.isOnKeyguard()
- ? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade)
- : getWidth();
- setBackgroundWidth((int) actualWidth);
- if (mShelfIcons != null) {
- mShelfIcons.setActualLayoutWidth((int) actualWidth);
- }
- mActualWidth = actualWidth;
- }
-
private void setActualWidth(float actualWidth) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
setBackgroundWidth((int) actualWidth);
if (mShelfIcons != null) {
mShelfIcons.setActualLayoutWidth((int) actualWidth);
@@ -482,25 +451,17 @@
final float fractionToShade = Interpolators.STANDARD.getInterpolation(
mAmbientState.getFractionToShade());
- if (NotificationIconContainerRefactor.isEnabled()) {
- if (mAmbientState.isOnKeyguard()) {
- float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1);
- float shortestWidth = mShelfIcons.calculateWidthFor(numViews);
- float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade);
- setActualWidth(actualWidth);
- } else {
- setActualWidth(getWidth());
- }
+ if (mAmbientState.isOnKeyguard()) {
+ float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1);
+ float shortestWidth = mShelfIcons.calculateWidthFor(numViews);
+ float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade);
+ setActualWidth(actualWidth);
} else {
- final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf);
- updateActualWidth(fractionToShade, shortestWidth);
+ setActualWidth(getWidth());
}
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
- if (!NotificationIconContainerRefactor.isEnabled()) {
- mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
- }
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
for (int i = 0; i < getHostLayoutChildCount(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index bbf0ae1..3068460 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -64,7 +64,6 @@
import com.android.systemui.statusbar.notification.NotificationContentDescription;
import com.android.systemui.statusbar.notification.NotificationDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.drawable.DrawableSize;
import java.lang.annotation.Retention;
@@ -906,12 +905,7 @@
return mDotAppearAmount;
}
- public void setDozing(boolean dozing, boolean animate, long delay) {
- setDozing(dozing, animate, delay, /* onChildCompleted= */ null);
- }
-
public void setTintAlpha(float tintAlpha) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
setDozeAmount(tintAlpha);
}
@@ -921,15 +915,6 @@
updateIconColor();
}
- public void setDozing(boolean dozing, boolean animate, long delay,
- @Nullable Runnable endRunnable) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mDozer.setDozing(f -> {
- setDozeAmount(f);
- updateAllowAnimation();
- }, dozing, animate, delay, this, endRunnable);
- }
-
private void updateAllowAnimation() {
if (mDozeAmount == 0 || mDozeAmount == 1) {
setAllowAnimation(mDozeAmount == 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index 6ba4fef..9e6cacb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.chips.ui.viewmodel.ChipTransitionHelper
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -64,7 +65,8 @@
@StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
- private val internalChip =
+ /** A direct mapping from [ScreenRecordChipModel] to [OngoingActivityChipModel]. */
+ private val simpleChip =
interactor.screenRecordState
.map { state ->
when (state) {
@@ -105,10 +107,31 @@
// See b/347726238 for [SharingStarted.Lazily] reasoning.
.stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+ /**
+ * The screen record chip to show that also ensures that the start time doesn't change once we
+ * enter the recording state. If we change the start time while we're recording, the chronometer
+ * could skip a second. See b/349620526.
+ */
+ private val chipWithConsistentTimer: StateFlow<OngoingActivityChipModel> =
+ simpleChip
+ .pairwise(initialValue = OngoingActivityChipModel.Hidden())
+ .map { (old, new) ->
+ if (
+ old is OngoingActivityChipModel.Shown.Timer &&
+ new is OngoingActivityChipModel.Shown.Timer
+ ) {
+ new.copy(startTimeMs = old.startTimeMs)
+ } else {
+ new
+ }
+ }
+ // See b/347726238 for [SharingStarted.Lazily] reasoning.
+ .stateIn(scope, SharingStarted.Lazily, OngoingActivityChipModel.Hidden())
+
private val chipTransitionHelper = ChipTransitionHelper(scope)
override val chip: StateFlow<OngoingActivityChipModel> =
- chipTransitionHelper.createChipFlow(internalChip)
+ chipTransitionHelper.createChipFlow(chipWithConsistentTimer)
private fun createDelegate(
recordedTask: ActivityManager.RunningTaskInfo?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 130b117..8a5165d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.content.res.ColorStateList
-import android.view.ContextThemeWrapper
import androidx.annotation.ColorInt
import com.android.settingslib.Utils
import com.android.systemui.res.R
@@ -43,9 +42,7 @@
/** The chip should have a red background with white text. */
data object Red : ColorsModel {
override fun background(context: Context): ColorStateList {
- val themedContext =
- ContextThemeWrapper(context, com.android.internal.R.style.Theme_DeviceDefault_Light)
- return Utils.getColorAttr(themedContext, com.android.internal.R.attr.materialColorError)
+ return ColorStateList.valueOf(context.getColor(R.color.GM2_red_700))
}
override fun text(context: Context) = context.getColor(android.R.color.white)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
index 0d5ade7..c7b3c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt
@@ -103,7 +103,7 @@
mSwipeDistanceThreshold =
r.getDimensionPixelSize(R.dimen.system_gestures_distance_threshold)
val display = DisplayManagerGlobal.getInstance().getRealDisplay(mContext.displayId)
- val displayCutout = display.cutout
+ val displayCutout = display?.cutout
if (displayCutout != null) {
// Expand swipe start threshold such that we can catch touches that just start beyond
// the notch area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 9240c1c..22c537c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -34,7 +34,6 @@
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.DozeParameters
@@ -223,11 +222,6 @@
val nowExpanding = isPulseExpanding()
val changed = nowExpanding != pulseExpanding
pulseExpanding = nowExpanding
- if (!NotificationIconContainerRefactor.isEnabled) {
- for (listener in wakeUpListeners) {
- listener.onPulseExpansionAmountChanged(changed)
- }
- }
if (changed) {
for (listener in wakeUpListeners) {
listener.onPulseExpandingChanged(pulseExpanding)
@@ -683,17 +677,6 @@
fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
/**
- * Called whenever the pulseExpansion changes
- *
- * @param expandingChanged if the user has started or stopped expanding
- */
- @Deprecated(
- message = "Use onPulseExpandedChanged instead.",
- replaceWith = ReplaceWith("onPulseExpandedChanged"),
- )
- fun onPulseExpansionAmountChanged(expandingChanged: Boolean) {}
-
- /**
* Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
* after the start delay, or after it ends/is cancelled.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 1511abd..f74c9a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -28,9 +28,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import javax.inject.Inject
@@ -43,7 +41,6 @@
@Inject
internal constructor(
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
- private val notificationIconAreaController: NotificationIconAreaController,
private val renderListInteractor: RenderNotificationListInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
private val sensitiveNotificationProtectionController:
@@ -63,12 +60,7 @@
} else {
controller.setNotifStats(notifStats)
}
- if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
- renderListInteractor.setRenderedList(entries)
- }
- if (!NotificationIconContainerRefactor.isEnabled) {
- notificationIconAreaController.updateNotificationIcons(entries)
- }
+ renderListInteractor.setRenderedList(entries)
}
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -76,9 +68,10 @@
var hasClearableAlertingNotifs = false
var hasNonClearableSilentNotifs = false
var hasClearableSilentNotifs = false
- val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
- screenshareNotificationHidingBugFix() &&
- sensitiveNotificationProtectionController.isSensitiveStateActive
+ val isSensitiveContentProtectionActive =
+ screenshareNotificationHiding() &&
+ screenshareNotificationHidingBugFix() &&
+ sensitiveNotificationProtectionController.isSensitiveStateActive
entries.forEach {
val section = checkNotNull(it.section) { "Null section for ${it.key}" }
val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 1cb59f1..1027bc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -81,11 +81,15 @@
* [CallType.Ongoing].
*/
val ongoingCallNotification: Flow<ActiveNotificationModel?> =
- allRepresentativeNotifications.map { notifMap ->
- // Once a call has started, its `whenTime` should stay the same, so we can use it as a
- // stable sort value.
- notifMap.values.filter { it.callType == CallType.Ongoing }.minByOrNull { it.whenTime }
- }
+ allRepresentativeNotifications
+ .map { notifMap ->
+ // Once a call has started, its `whenTime` should stay the same, so we can use it as
+ // a stable sort value.
+ notifMap.values
+ .filter { it.callType == CallType.Ongoing }
+ .minByOrNull { it.whenTime }
+ }
+ .flowOn(backgroundDispatcher)
/** Are any notifications being actively presented in the notification stack? */
val areAnyNotificationsPresent: Flow<Boolean> =
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
deleted file mode 100644
index 1bcab3f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-
-import android.content.Context
-import android.graphics.Rect
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
-import com.android.systemui.statusbar.phone.NotificationIconContainer
-import javax.inject.Inject
-
-/**
- * Controller class for [NotificationIconContainer]. This implementation serves as a temporary
- * wrapper around [NotificationIconContainerViewBinder], so that external code can continue to
- * depend on the [NotificationIconAreaController] interface. Once
- * [LegacyNotificationIconAreaControllerImpl] is removed, this class can go away and the ViewBinder
- * can be used directly.
- */
-@SysUISingleton
-class NotificationIconAreaControllerViewBinderWrapperImpl @Inject constructor() :
- NotificationIconAreaController {
-
- /** Called by the Keyguard*ViewController whose view contains the aod icons. */
- override fun setupAodIcons(aodIcons: NotificationIconContainer?) = unsupported
-
- override fun setShelfIcons(icons: NotificationIconContainer) = unsupported
-
- override fun onDensityOrFontScaleChanged(context: Context) = unsupported
-
- /** Returns the view that represents the notification area. */
- override fun getNotificationInnerAreaView(): View? = unsupported
-
- /** Updates the notifications with the given list of notifications to display. */
- override fun updateNotificationIcons(entries: List<ListEntry>) = unsupported
-
- override fun updateAodNotificationIcons() = unsupported
-
- override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported
-
- override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) =
- unsupported
-
- override fun setAnimationsEnabled(enabled: Boolean) = unsupported
-
- override fun onThemeChanged() = unsupported
-
- override fun getHeight(): Int = unsupported
-
- companion object {
- val unsupported: Nothing
- get() =
- error(
- "Code path not supported when ${NotificationIconContainerRefactor.FLAG_NAME}" +
- " is disabled"
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
index 0c114a2..931381f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt
@@ -17,7 +17,6 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
import dagger.Binds
@@ -40,7 +39,6 @@
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
- if (!NotificationIconContainerRefactor.isEnabled) return
pw.asIndenting().run {
printCollection("AOD Icon binding failures:", aodFailures)
printCollection("Status Bar Icon binding failures:", statusBarFailures)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d2d0aaa..560028c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -46,7 +46,6 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -236,16 +235,6 @@
setOutlineAlpha(alpha);
}
- @Override
- public void setBelowSpeedBump(boolean below) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- super.setBelowSpeedBump(below);
- if (below != mIsBelowSpeedBump) {
- mIsBelowSpeedBump = below;
- updateBackgroundTint();
- }
- }
-
/**
* Sets the tint color of the background
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 6becbd2..afda426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -40,7 +40,6 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.RoundableState;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.util.Compile;
@@ -394,14 +393,6 @@
public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
Runnable onEndRunnable);
- /**
- * Set the notification appearance to be below the speed bump.
- * @param below true if it is below.
- */
- public void setBelowSpeedBump(boolean below) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- }
-
public int getPinnedHeadsUpHeight() {
return getIntrinsicHeight();
}
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
deleted file mode 100644
index a08af75..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationIconContainerRefactor.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.notification.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the NotificationIconContainer refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object NotificationIconContainerRefactor {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_ICON_CONTAINER_REFACTOR
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** 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 819527e..15dc115 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
@@ -21,9 +21,7 @@
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@@ -35,17 +33,10 @@
viewModel: NotificationShelfViewModel,
falsingManager: FalsingManager,
nicBinder: NotificationIconContainerShelfViewBinder,
- notificationIconAreaController: NotificationIconAreaController,
): Unit = coroutineScope {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
shelf.apply {
- traceSection("NotifShelf#bindShelfIcons") {
- if (NotificationIconContainerRefactor.isEnabled) {
- launch { nicBinder.bind(shelfIcons) }
- } else {
- notificationIconAreaController.setShelfIcons(shelfIcons)
- }
- }
+ traceSection("NotifShelf#bindShelfIcons") { launch { nicBinder.bind(shelfIcons) } }
launch {
viewModel.canModifyColorOfNotifications.collect(::setCanModifyColorOfNotifications)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 83de226..69c9a4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -26,7 +26,6 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
/**
* A state of an expandable view
@@ -157,11 +156,6 @@
expandableView.setHideSensitive(
this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
- // apply below shelf speed bump
- if (!NotificationIconContainerRefactor.isEnabled()) {
- expandableView.setBelowSpeedBump(this.belowSpeedBump);
- }
-
// apply clipping
final float oldClipTopAmount = expandableView.getClipTopAmount();
if (oldClipTopAmount != this.clipTopAmount) {
@@ -211,11 +205,6 @@
abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
}
- // apply below the speed bump
- if (!NotificationIconContainerRefactor.isEnabled()) {
- expandableView.setBelowSpeedBump(this.belowSpeedBump);
- }
-
// start hiding sensitive animation
expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
properties.delay, properties.duration);
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 608fe95..41195aa 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
@@ -1426,6 +1426,7 @@
}
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setHeadsUpBoundaries(height, bottomBarHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 9b21fa9..5d37476 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -18,13 +18,14 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
import android.content.Context
-import com.android.systemui.Flags.centralizedStatusBarHeightFix
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.policy.SplitShadeStateController
import dagger.Lazy
import javax.inject.Inject
@@ -44,7 +45,8 @@
constructor(
configurationRepository: ConfigurationRepository,
private val context: Context,
- private val splitShadeStateController: SplitShadeStateController,
+ private val splitShadeStateController: Lazy<SplitShadeStateController>,
+ private val shadeInteractor: Lazy<ShadeInteractor>,
keyguardInteractor: KeyguardInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
largeScreenHeaderHelperLazy: Lazy<LargeScreenHeaderHelper>,
@@ -57,16 +59,33 @@
/** An internal modification was made to notifications */
val notificationStackChanged = _notificationStackChanged.debounce(20L)
+ private val configurationChangeEvents =
+ configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }
+
+ /* Warning: Even though the value it emits only contains the split shade status, this flow must
+ * emit a value whenever the configuration *or* the split shade status changes. Adding a
+ * distinctUntilChanged() to this would cause configurationBasedDimensions to miss configuration
+ * updates that affect other resources, like margins or the large screen header flag.
+ */
+ private val dimensionsUpdateEventsWithShouldUseSplitShade: Flow<Boolean> =
+ if (SceneContainerFlag.isEnabled) {
+ combine(configurationChangeEvents, shadeInteractor.get().isShadeLayoutWide) {
+ _,
+ isShadeLayoutWide ->
+ isShadeLayoutWide
+ }
+ } else {
+ configurationChangeEvents.map {
+ splitShadeStateController.get().shouldUseSplitNotificationShade(context.resources)
+ }
+ }
+
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
- configurationRepository.onAnyConfigurationChange
- .onStart { emit(Unit) }
- .map { _ ->
+ dimensionsUpdateEventsWithShouldUseSplitShade
+ .map { shouldUseSplitShade ->
with(context.resources) {
ConfigurationBasedDimensions(
- useSplitShade =
- splitShadeStateController.shouldUseSplitNotificationShade(
- context.resources
- ),
+ useSplitShade = shouldUseSplitShade,
useLargeScreenHeader =
getBoolean(R.bool.config_use_large_screen_shade_header),
marginHorizontal =
@@ -75,11 +94,7 @@
getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
marginTopLargeScreen =
- if (centralizedStatusBarHeightFix()) {
- largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
- } else {
- getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
- },
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight(),
keyguardSplitShadeTopMargin =
getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin),
)
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 5544f93..5572f8e 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
@@ -46,7 +46,6 @@
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.notification.ui.viewbinder.HeadsUpNotificationViewBinder
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.util.kotlin.awaitCancellationThenDispose
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.ui.isAnimating
@@ -74,7 +73,6 @@
private val configuration: ConfigurationState,
private val falsingManager: FalsingManager,
private val hunBinder: HeadsUpNotificationViewBinder,
- private val iconAreaController: NotificationIconAreaController,
private val loggerOptional: Optional<NotificationStatsLogger>,
private val metricsLogger: MetricsLogger,
private val nicBinder: NotificationIconContainerShelfViewBinder,
@@ -128,7 +126,6 @@
viewModel.shelf,
falsingManager,
nicBinder,
- iconAreaController,
)
}
@@ -183,12 +180,12 @@
launchNotificationSettings = { view ->
notificationActivityStarter
.get()
- .startHistoryIntent(view, /* showHistory = */ false)
+ .startHistoryIntent(view, /* showHistory= */ false)
},
launchNotificationHistory = { view ->
notificationActivityStarter
.get()
- .startHistoryIntent(view, /* showHistory = */ true)
+ .startHistoryIntent(view, /* showHistory= */ true)
},
)
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 0541550..aa1911e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -99,19 +99,20 @@
disposables +=
view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- // Only temporarily needed, until flexi notifs go live
- viewModel.shadeCollapseFadeIn.collect { fadeIn ->
- if (fadeIn) {
- android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 250
- addUpdateListener { animation ->
- controller.setMaxAlphaForKeyguard(
- animation.animatedFraction,
- "SharedNotificationContainerVB (collapseFadeIn)"
- )
+ if (!SceneContainerFlag.isEnabled) {
+ launch {
+ viewModel.shadeCollapseFadeIn.collect { fadeIn ->
+ if (fadeIn) {
+ android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 250
+ addUpdateListener { animation ->
+ controller.setMaxAlphaForKeyguard(
+ animation.animatedFraction,
+ "SharedNotificationContainerVB (collapseFadeIn)"
+ )
+ }
+ start()
}
- start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index e8a7840..d179888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -22,7 +22,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -30,7 +29,6 @@
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -43,7 +41,6 @@
dumpManager: DumpManager,
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
- private val shadeSceneViewModel: ShadeSceneViewModel,
private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
featureFlags: FeatureFlagsClassic,
) : FlowDumperImpl(dumpManager) {
@@ -63,19 +60,11 @@
interactor.setConstrainedAvailableSpace(height)
}
- /** Notifies that empty space on the notification scrim has been clicked. */
- fun onEmptySpaceClicked() {
- shadeSceneViewModel.onContentClicked()
- }
-
/** Sets the content alpha for the current state of the brightness mirror */
fun setAlphaForBrightnessMirror(alpha: Float) {
interactor.setAlphaForBrightnessMirror(alpha)
}
- /** Whether or not the notification scrim should be clickable. */
- val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable
-
/** True when a HUN is pinned or animating away. */
val isHeadsUpOrAnimatingAway: Flow<Boolean> =
headsUpNotificationInteractor.isHeadsUpOrAnimatingAway
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3a3b05b..db8cd57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -209,7 +209,6 @@
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.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -237,10 +236,10 @@
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dalvik.annotation.optimization.NeverCompile;
-
import dagger.Lazy;
+import dalvik.annotation.optimization.NeverCompile;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
@@ -546,7 +545,6 @@
protected final BatteryController mBatteryController;
private UiModeManager mUiModeManager;
private LogMaker mStatusBarStateLog;
- protected final NotificationIconAreaController mNotificationIconAreaController;
@Nullable private View mAmbientIndicationContainer;
private final SysuiColorExtractor mColorExtractor;
private final ScreenLifecycle mScreenLifecycle;
@@ -683,7 +681,6 @@
DemoModeController demoModeController,
Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
- NotificationIconAreaController notificationIconAreaController,
BrightnessSliderController.Factory brightnessSliderFactory,
ScreenOffAnimationController screenOffAnimationController,
WallpaperController wallpaperController,
@@ -787,7 +784,6 @@
mUserInfoControllerImpl = userInfoControllerImpl;
mIconPolicy = phoneStatusBarPolicy;
mDemoModeController = demoModeController;
- mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
mWallpaperController = wallpaperController;
mStatusBarSignalPolicy = statusBarSignalPolicy;
@@ -2273,7 +2269,10 @@
// applying the dimming effect twice.
mUiBgExecutor.execute(() -> {
float dimAmount = 0f;
- if (mWallpaperManager.lockScreenWallpaperExists()) {
+ // Note that access to WallpaperManager APIs should be guarded by a check into
+ // WallpaperManager#isWallpaperSupported. Form factors that do not use wallpaper
+ // may crash SysUI during improper access. ref: b/355307617
+ if (!mWallpaperSupported || mWallpaperManager.lockScreenWallpaperExists()) {
dimAmount = mWallpaperManager.getWallpaperDimAmount();
}
final float scrimDimAmount = dimAmount;
@@ -2652,9 +2651,6 @@
!mDozeServiceHost.isPulsing());
mShadeSurface.setTouchAndAnimationDisabled(disabled);
- if (!NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaController.setAnimationsEnabled(!disabled);
- }
}
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@@ -2989,7 +2985,7 @@
@Override
public void onFalse() {
// Hides quick settings, bouncer, and quick-quick settings.
- mStatusBarKeyguardViewManager.reset(true);
+ mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true);
}
};
@@ -3053,9 +3049,6 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- if (!NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
- }
}
@Override
@@ -3073,9 +3066,6 @@
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
}
- if (!NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaController.onThemeChanged();
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ca1fb78b..f13a593 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -52,7 +52,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -106,7 +105,6 @@
private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final AuthController mAuthController;
- private final NotificationIconAreaController mNotificationIconAreaController;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final ShadeLockscreenInteractor mShadeLockscreenInteractor;
private View mAmbientIndicationContainer;
@@ -129,7 +127,6 @@
NotificationShadeWindowController notificationShadeWindowController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
AuthController authController,
- NotificationIconAreaController notificationIconAreaController,
ShadeLockscreenInteractor shadeLockscreenInteractor,
DozeInteractor dozeInteractor) {
super();
@@ -149,7 +146,6 @@
mNotificationShadeWindowController = notificationShadeWindowController;
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mAuthController = authController;
- mNotificationIconAreaController = notificationIconAreaController;
mShadeLockscreenInteractor = shadeLockscreenInteractor;
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mDozeInteractor = dozeInteractor;
@@ -196,13 +192,8 @@
void fireNotificationPulse(NotificationEntry entry) {
Runnable pulseSuppressedListener = () -> {
- if (NotificationIconContainerRefactor.isEnabled()) {
- mHeadsUpManager.removeNotification(
- entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
- } else {
- entry.setPulseSuppressed(true);
- mNotificationIconAreaController.updateAodNotificationIcons();
- }
+ mHeadsUpManager.removeNotification(
+ entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
};
Assert.isMainThread();
for (Callback callback : mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f99a81e..8f94c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -42,7 +42,6 @@
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
@@ -74,7 +73,6 @@
private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
private static final SourceType PULSING = SourceType.from("Pulsing");
- private final NotificationIconAreaController mNotificationIconAreaController;
private final HeadsUpManager mHeadsUpManager;
private final NotificationStackScrollLayoutController mStackScrollerController;
@@ -114,7 +112,6 @@
@VisibleForTesting
@Inject
public HeadsUpAppearanceController(
- NotificationIconAreaController notificationIconAreaController,
HeadsUpManager headsUpManager,
StatusBarStateController stateController,
PhoneStatusBarTransitions phoneStatusBarTransitions,
@@ -132,7 +129,6 @@
HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
- mNotificationIconAreaController = notificationIconAreaController;
mNotificationRoundnessManager = notificationRoundnessManager;
mHeadsUpManager = headsUpManager;
@@ -178,11 +174,8 @@
@Override
protected void onViewAttached() {
mHeadsUpManager.addListener(this);
- mView.setOnDrawingRectChangedListener(
- () -> updateIsolatedIconLocation(true /* requireUpdate */));
- if (NotificationIconContainerRefactor.isEnabled()) {
- updateIsolatedIconLocation(true);
- }
+ mView.setOnDrawingRectChangedListener(this::updateIsolatedIconLocation);
+ updateIsolatedIconLocation();
mWakeUpCoordinator.addListener(this);
getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
@@ -198,9 +191,7 @@
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
mView.setOnDrawingRectChangedListener(null);
- if (NotificationIconContainerRefactor.isEnabled()) {
- mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
- }
+ mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
mWakeUpCoordinator.removeListener(this);
getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
@@ -208,14 +199,8 @@
mDarkIconDispatcher.removeDarkReceiver(this);
}
- private void updateIsolatedIconLocation(boolean requireStateUpdate) {
- if (NotificationIconContainerRefactor.isEnabled()) {
- mHeadsUpNotificationIconInteractor
- .setIsolatedIconLocation(mView.getIconDrawingRect());
- } else {
- mNotificationIconAreaController.setIsolatedIconLocation(
- mView.getIconDrawingRect(), requireStateUpdate);
- }
+ private void updateIsolatedIconLocation() {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(mView.getIconDrawingRect());
}
@Override
@@ -251,14 +236,8 @@
setShown(true);
animateIsolation = !isExpanded();
}
- if (NotificationIconContainerRefactor.isEnabled()) {
- mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
- newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
- } else {
- updateIsolatedIconLocation(false /* requireUpdate */);
- mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
- : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
- }
+ mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
+ newEntry == null ? null : newEntry.getRepresentativeEntry().getKey());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 0adc1b0..013903a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInScale;
import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate;
@@ -169,9 +168,7 @@
mStatusViewBottomMargin =
res.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin);
mSplitShadeTopNotificationsMargin =
- centralizedStatusBarHeightFix()
- ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context)
- : res.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
+ LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context);
mSplitShadeTargetTopMargin =
res.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 84e6018..c3da7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Flags.centralizedStatusBarHeightFix;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard;
@@ -133,9 +132,6 @@
mUserSwitcherContainer = findViewById(R.id.user_switcher_container);
mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot);
loadDimens();
- if (!centralizedStatusBarHeightFix()) {
- setGravity(Gravity.CENTER_VERTICAL);
- }
}
/**
@@ -322,7 +318,7 @@
final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
? Math.max(mMinDotWidth, mPadding.right) : mPadding.right;
- int top = centralizedStatusBarHeightFix() ? waterfallTop + mPadding.top : waterfallTop;
+ int top = waterfallTop + mPadding.top;
setPadding(minLeft, top, minRight, 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
deleted file mode 100644
index f84efbb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ /dev/null
@@ -1,700 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.Flags.newAodTransition;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Trace;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.collection.ArrayMap;
-
-import com.android.app.animation.Interpolators;
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.settingslib.Utils;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyguard.MigrateClocksToBlueprint;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Function;
-
-import javax.inject.Inject;
-
-/**
- * A controller for the space in the status bar to the left of the system icons. This area is
- * normally reserved for notifications.
- */
-@SysUISingleton
-public class LegacyNotificationIconAreaControllerImpl implements
- NotificationIconAreaController,
- DarkReceiver,
- StatusBarStateController.StateListener,
- NotificationWakeUpCoordinator.WakeUpListener,
- DemoMode {
-
- private static final long AOD_ICONS_APPEAR_DURATION = 200;
- @ColorInt
- private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
-
- private final ContrastColorUtil mContrastColorUtil;
- private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
- private final StatusBarStateController mStatusBarStateController;
- private final NotificationMediaManager mMediaManager;
- private final NotificationWakeUpCoordinator mWakeUpCoordinator;
- private final KeyguardBypassController mBypassController;
- private final DozeParameters mDozeParameters;
- private final SectionStyleProvider mSectionStyleProvider;
- private final Optional<Bubbles> mBubblesOptional;
- private final StatusBarWindowController mStatusBarWindowController;
- private final ScreenOffAnimationController mScreenOffAnimationController;
-
- private int mIconSize;
- private int mIconHPadding;
- private int mIconTint = Color.WHITE;
-
- private List<ListEntry> mNotificationEntries = List.of();
- protected View mNotificationIconArea;
- private NotificationIconContainer mNotificationIcons;
- private NotificationIconContainer mShelfIcons;
- private NotificationIconContainer mAodIcons;
- private final ArrayList<Rect> mTintAreas = new ArrayList<>();
- private final Context mContext;
- private int mAodIconAppearTranslation;
-
- private boolean mAnimationsEnabled;
- private int mAodIconTint;
- private boolean mAodIconsVisible;
- private boolean mShowLowPriority = true;
-
- @VisibleForTesting
- final NotificationListener.NotificationSettingsListener mSettingsListener =
- new NotificationListener.NotificationSettingsListener() {
- @Override
- public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) {
- mShowLowPriority = !hideSilentStatusIcons;
- updateStatusBarIcons();
- }
- };
-
- @Inject
- public LegacyNotificationIconAreaControllerImpl(
- Context context,
- StatusBarStateController statusBarStateController,
- NotificationWakeUpCoordinator wakeUpCoordinator,
- KeyguardBypassController keyguardBypassController,
- NotificationMediaManager notificationMediaManager,
- NotificationListener notificationListener,
- DozeParameters dozeParameters,
- SectionStyleProvider sectionStyleProvider,
- Optional<Bubbles> bubblesOptional,
- DemoModeController demoModeController,
- DarkIconDispatcher darkIconDispatcher,
- FeatureFlags featureFlags,
- StatusBarWindowController statusBarWindowController,
- ScreenOffAnimationController screenOffAnimationController) {
- mContrastColorUtil = ContrastColorUtil.getInstance(context);
- mContext = context;
- mStatusBarStateController = statusBarStateController;
- mStatusBarStateController.addCallback(this);
- mMediaManager = notificationMediaManager;
- mDozeParameters = dozeParameters;
- mSectionStyleProvider = sectionStyleProvider;
- mWakeUpCoordinator = wakeUpCoordinator;
- wakeUpCoordinator.addListener(this);
- mBypassController = keyguardBypassController;
- mBubblesOptional = bubblesOptional;
- demoModeController.addCallback(this);
- mStatusBarWindowController = statusBarWindowController;
- mScreenOffAnimationController = screenOffAnimationController;
- notificationListener.addNotificationSettingsListener(mSettingsListener);
- initializeNotificationAreaViews(context);
- reloadAodColor();
- darkIconDispatcher.addDarkReceiver(this);
- }
-
- protected View inflateIconArea(LayoutInflater inflater) {
- return inflater.inflate(R.layout.notification_icon_area, null);
- }
-
- /**
- * Initializes the views that will represent the notification area.
- */
- protected void initializeNotificationAreaViews(Context context) {
- reloadDimens(context);
-
- LayoutInflater layoutInflater = LayoutInflater.from(context);
- mNotificationIconArea = inflateIconArea(layoutInflater);
- mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
- }
-
- /**
- * Called by the Keyguard*ViewController whose view contains the aod icons.
- */
- public void setupAodIcons(@NonNull NotificationIconContainer aodIcons) {
- boolean changed = mAodIcons != null && aodIcons != mAodIcons;
- if (changed) {
- mAodIcons.setAnimationsEnabled(false);
- mAodIcons.removeAllViews();
- }
- mAodIcons = aodIcons;
- mAodIcons.setOnLockScreen(true);
- updateAodIconsVisibility(false /* animate */, changed);
- updateAnimations();
- if (changed) {
- updateAodNotificationIcons();
- }
- updateIconLayoutParams(mContext);
- }
-
- public void setShelfIcons(NotificationIconContainer icons) {
- mShelfIcons = icons;
- }
-
- public void onDensityOrFontScaleChanged(@NotNull Context context) {
- updateIconLayoutParams(context);
- }
-
- private void updateIconLayoutParams(Context context) {
- reloadDimens(context);
- final FrameLayout.LayoutParams params = generateIconLayoutParams();
- for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
- View child = mNotificationIcons.getChildAt(i);
- child.setLayoutParams(params);
- }
- if (mShelfIcons != null) {
- for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
- View child = mShelfIcons.getChildAt(i);
- child.setLayoutParams(params);
- }
- }
- if (mAodIcons != null) {
- for (int i = 0; i < mAodIcons.getChildCount(); i++) {
- View child = mAodIcons.getChildAt(i);
- child.setLayoutParams(params);
- }
- }
- }
-
- @NonNull
- private FrameLayout.LayoutParams generateIconLayoutParams() {
- return new FrameLayout.LayoutParams(
- mIconSize + 2 * mIconHPadding, mStatusBarWindowController.getStatusBarHeight());
- }
-
- private void reloadDimens(Context context) {
- Resources res = context.getResources();
- mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp);
- mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin);
- mAodIconAppearTranslation = res.getDimensionPixelSize(
- R.dimen.shelf_appear_translation);
- }
-
- /**
- * Returns the view that represents the notification area.
- */
- public View getNotificationInnerAreaView() {
- return mNotificationIconArea;
- }
-
- /**
- * See {@link com.android.systemui.statusbar.policy.DarkIconDispatcher#setIconsDarkArea}.
- * Sets the color that should be used to tint any icons in the notification area.
- *
- * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
- * @param darkIntensity
- */
- public void onDarkChanged(ArrayList<Rect> tintAreas, float darkIntensity, int iconTint) {
- mTintAreas.clear();
- mTintAreas.addAll(tintAreas);
-
- if (DarkIconDispatcher.isInAreas(tintAreas, mNotificationIconArea)) {
- mIconTint = iconTint;
- }
-
- applyNotificationIconsTint();
- }
-
- protected boolean shouldShowNotificationIcon(NotificationEntry entry,
- boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
- boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hidePulsing) {
- if (!showAmbient && mSectionStyleProvider.isMinimized(entry)) {
- return false;
- }
- if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) {
- return false;
- }
- if (!showLowPriority && mSectionStyleProvider.isSilent(entry)) {
- return false;
- }
- if (entry.isRowDismissed() && hideDismissed) {
- return false;
- }
- if (hideRepliedMessages && entry.isLastMessageFromReply()) {
- return false;
- }
- // showAmbient == show in shade but not shelf
- if (!showAmbient && entry.shouldSuppressStatusBar()) {
- return false;
- }
- if (hidePulsing && entry.showingPulsing()
- && (!mWakeUpCoordinator.getNotificationsFullyHidden()
- || !entry.isPulseSuppressed())) {
- return false;
- }
- if (mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleExpanded(entry.getKey())) {
- return false;
- }
- return true;
- }
-
- /**
- * Updates the notifications with the given list of notifications to display.
- */
- public void updateNotificationIcons(List<ListEntry> entries) {
- mNotificationEntries = entries;
- updateNotificationIcons();
- }
-
- private void updateNotificationIcons() {
- Trace.beginSection("NotificationIconAreaController.updateNotificationIcons");
- updateStatusBarIcons();
- updateShelfIcons();
- updateAodNotificationIcons();
-
- applyNotificationIconsTint();
- Trace.endSection();
- }
-
- private void updateShelfIcons() {
- if (mShelfIcons == null) {
- return;
- }
- updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons,
- true /* showAmbient */,
- true /* showLowPriority */,
- false /* hideDismissed */,
- false /* hideRepliedMessages */,
- false /* hideCurrentMedia */,
- false /* hidePulsing */);
- }
-
- public void updateStatusBarIcons() {
- updateIconsForLayout(entry -> entry.getIcons().getStatusBarIcon(), mNotificationIcons,
- false /* showAmbient */,
- mShowLowPriority,
- true /* hideDismissed */,
- true /* hideRepliedMessages */,
- false /* hideCurrentMedia */,
- false /* hidePulsing */);
- }
-
- public void updateAodNotificationIcons() {
- if (mAodIcons == null) {
- return;
- }
- updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
- false /* showAmbient */,
- true /* showLowPriority */,
- true /* hideDismissed */,
- true /* hideRepliedMessages */,
- true /* hideCurrentMedia */,
- mBypassController.getBypassEnabled() /* hidePulsing */);
- }
-
- @VisibleForTesting
- boolean shouldShouldLowPriorityIcons() {
- return mShowLowPriority;
- }
-
- /**
- * Updates the notification icons for a host layout. This will ensure that the notification
- * host layout will have the same icons like the ones in here.
- * @param function A function to look up an icon view based on an entry
- * @param hostLayout which layout should be updated
- * @param showAmbient should ambient notification icons be shown
- * @param showLowPriority should icons from silent notifications be shown
- * @param hideDismissed should dismissed icons be hidden
- * @param hideRepliedMessages should messages that have been replied to be hidden
- * @param hidePulsing should pulsing notifications be hidden
- */
- private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
- NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
- boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
- boolean hidePulsing) {
- ArrayList<StatusBarIconView> toShow = new ArrayList<>(mNotificationEntries.size());
- // Filter out ambient notifications and notification children.
- for (int i = 0; i < mNotificationEntries.size(); i++) {
- NotificationEntry entry = mNotificationEntries.get(i).getRepresentativeEntry();
- if (entry != null && entry.getRow() != null) {
- if (shouldShowNotificationIcon(entry, showAmbient, showLowPriority, hideDismissed,
- hideRepliedMessages, hideCurrentMedia, hidePulsing)) {
- StatusBarIconView iconView = function.apply(entry);
- if (iconView != null) {
- toShow.add(iconView);
- }
- }
- }
- }
-
- // In case we are changing the suppression of a group, the replacement shouldn't flicker
- // and it should just be replaced instead. We therefore look for notifications that were
- // just replaced by the child or vice-versa to suppress this.
-
- ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
- ArrayList<View> toRemove = new ArrayList<>();
- for (int i = 0; i < hostLayout.getChildCount(); i++) {
- View child = hostLayout.getChildAt(i);
- if (!(child instanceof StatusBarIconView)) {
- continue;
- }
- if (!toShow.contains(child)) {
- boolean iconWasReplaced = false;
- StatusBarIconView removedIcon = (StatusBarIconView) child;
- String removedGroupKey = removedIcon.getNotification().getGroupKey();
- for (int j = 0; j < toShow.size(); j++) {
- StatusBarIconView candidate = toShow.get(j);
- if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
- && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
- if (!iconWasReplaced) {
- iconWasReplaced = true;
- } else {
- iconWasReplaced = false;
- break;
- }
- }
- }
- if (iconWasReplaced) {
- ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
- if (statusBarIcons == null) {
- statusBarIcons = new ArrayList<>();
- replacingIcons.put(removedGroupKey, statusBarIcons);
- }
- statusBarIcons.add(removedIcon.getStatusBarIcon());
- }
- toRemove.add(removedIcon);
- }
- }
- // removing all duplicates
- ArrayList<String> duplicates = new ArrayList<>();
- for (String key : replacingIcons.keySet()) {
- ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
- if (statusBarIcons.size() != 1) {
- duplicates.add(key);
- }
- }
- replacingIcons.removeAll(duplicates);
- hostLayout.setReplacingIconsLegacy(replacingIcons);
-
- final int toRemoveCount = toRemove.size();
- for (int i = 0; i < toRemoveCount; i++) {
- hostLayout.removeView(toRemove.get(i));
- }
-
- final FrameLayout.LayoutParams params = generateIconLayoutParams();
- for (int i = 0; i < toShow.size(); i++) {
- StatusBarIconView v = toShow.get(i);
- // The view might still be transiently added if it was just removed and added again
- hostLayout.removeTransientView(v);
- if (v.getParent() == null) {
- if (hideDismissed) {
- v.setOnDismissListener(mUpdateStatusBarIcons);
- }
- hostLayout.addView(v, i, params);
- }
- }
-
- hostLayout.setChangingViewPositions(true);
- // Re-sort notification icons
- final int childCount = hostLayout.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View actual = hostLayout.getChildAt(i);
- StatusBarIconView expected = toShow.get(i);
- if (actual == expected) {
- continue;
- }
- hostLayout.removeView(expected);
- hostLayout.addView(expected, i);
- }
- hostLayout.setChangingViewPositions(false);
- hostLayout.setReplacingIconsLegacy(null);
- }
-
- /**
- * Applies {@link #mIconTint} to the notification icons.
- */
- private void applyNotificationIconsTint() {
- for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
- final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
- if (iv.getWidth() != 0) {
- updateTintForIcon(iv, mIconTint);
- } else {
- iv.executeOnLayout(() -> updateTintForIcon(iv, mIconTint));
- }
- }
-
- updateAodIconColors();
- }
-
- private void updateTintForIcon(StatusBarIconView v, int tint) {
- boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
- int color = StatusBarIconView.NO_COLOR;
- boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mContrastColorUtil);
- if (colorize) {
- color = DarkIconDispatcher.getTint(mTintAreas, v, tint);
- }
- v.setStaticDrawableColor(color);
- v.setDecorColor(tint);
- }
-
- public void showIconIsolated(StatusBarIconView icon, boolean animated) {
- mNotificationIcons.showIconIsolatedLegacy(icon, animated);
- }
-
- public void setIsolatedIconLocation(@NotNull Rect iconDrawingRect, boolean requireStateUpdate) {
- mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
- }
-
- @Override
- public void onDozingChanged(boolean isDozing) {
- if (mAodIcons == null) {
- return;
- }
- boolean animate = mDozeParameters.getAlwaysOn()
- && !mDozeParameters.getDisplayNeedsBlanking();
- mAodIcons.setDozing(isDozing, animate, 0);
- }
-
- public void setAnimationsEnabled(boolean enabled) {
- mAnimationsEnabled = enabled;
- updateAnimations();
- }
-
- @Override
- public void onStateChanged(int newState) {
- updateAodIconsVisibility(false /* animate */, false /* force */);
- updateAnimations();
- }
-
- private void updateAnimations() {
- boolean inShade = mStatusBarStateController.getState() == StatusBarState.SHADE;
- if (mAodIcons != null) {
- mAodIcons.setAnimationsEnabled(mAnimationsEnabled && !inShade);
- }
- mNotificationIcons.setAnimationsEnabled(mAnimationsEnabled && inShade);
- }
-
- public void onThemeChanged() {
- reloadAodColor();
- updateAodIconColors();
- }
-
- public int getHeight() {
- return mAodIcons == null ? 0 : mAodIcons.getHeight();
- }
-
- public void appearAodIcons() {
- if (mAodIcons == null) {
- return;
- }
- if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mAodIcons.setTranslationY(-mAodIconAppearTranslation);
- }
- mAodIcons.setAlpha(0);
- animateInAodIconTranslation();
- mAodIcons.animate()
- .alpha(1)
- .setInterpolator(Interpolators.LINEAR)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start();
- } else {
- mAodIcons.setAlpha(1.0f);
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mAodIcons.setTranslationY(0);
- }
- }
- }
-
- private void animateInAodIconTranslation() {
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mAodIcons.animate()
- .setInterpolator(Interpolators.DECELERATE_QUINT)
- .translationY(0)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start();
- }
- }
-
- private void reloadAodColor() {
- mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
- R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR);
- }
-
- private void updateAodIconColors() {
- if (mAodIcons != null) {
- for (int i = 0; i < mAodIcons.getChildCount(); i++) {
- final StatusBarIconView iv = (StatusBarIconView) mAodIcons.getChildAt(i);
- if (iv.getWidth() != 0) {
- updateTintForIcon(iv, mAodIconTint);
- } else {
- iv.executeOnLayout(() -> updateTintForIcon(iv, mAodIconTint));
- }
- }
- }
- }
-
- @Override
- public void onFullyHiddenChanged(boolean fullyHidden) {
- boolean animate = true;
- if (!mBypassController.getBypassEnabled()) {
- animate = mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking();
- if (!newAodTransition()) {
- // We only want the appear animations to happen when the notifications get fully
- // hidden, since otherwise the unhide animation overlaps
- animate &= fullyHidden;
- }
- }
- updateAodIconsVisibility(animate, false /* force */);
- updateAodNotificationIcons();
- updateAodIconColors();
- }
-
- @Override
- public void onPulseExpansionAmountChanged(boolean expandingChanged) {
- if (expandingChanged) {
- updateAodIconsVisibility(true /* animate */, false /* force */);
- }
- }
-
- private void updateAodIconsVisibility(boolean animate, boolean forceUpdate) {
- if (mAodIcons == null) {
- return;
- }
- boolean visible = mBypassController.getBypassEnabled()
- || mWakeUpCoordinator.getNotificationsFullyHidden();
-
- // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
- // playing, in which case we want them to be visible since we're animating in the AOD UI and
- // will be switching to KEYGUARD shortly.
- if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD
- && !mScreenOffAnimationController.shouldShowAodIconsWhenShade()) {
- visible = false;
- }
- if (visible && mWakeUpCoordinator.isPulseExpanding()
- && !mBypassController.getBypassEnabled()) {
- visible = false;
- }
- if (mAodIconsVisible != visible || forceUpdate) {
- mAodIconsVisible = visible;
- mAodIcons.animate().cancel();
- if (animate) {
- if (newAodTransition()) {
- // Let's make sure the icon are translated to 0, since we cancelled it above
- animateInAodIconTranslation();
- if (mAodIconsVisible) {
- CrossFadeHelper.fadeIn(mAodIcons);
- } else {
- CrossFadeHelper.fadeOut(mAodIcons);
- }
- } else {
- boolean wasFullyInvisible = mAodIcons.getVisibility() != View.VISIBLE;
- if (mAodIconsVisible) {
- if (wasFullyInvisible) {
- // No fading here, let's just appear the icons instead!
- mAodIcons.setVisibility(View.VISIBLE);
- mAodIcons.setAlpha(1.0f);
- appearAodIcons();
- } else {
- // Let's make sure the icon are translated to 0, since we cancelled it
- // above
- animateInAodIconTranslation();
- // We were fading out, let's fade in instead
- CrossFadeHelper.fadeIn(mAodIcons);
- }
- } else {
- // Let's make sure the icon are translated to 0, since we cancelled it above
- animateInAodIconTranslation();
- CrossFadeHelper.fadeOut(mAodIcons);
- }
- }
- } else {
- mAodIcons.setAlpha(1.0f);
- if (!MigrateClocksToBlueprint.isEnabled()) {
- mAodIcons.setTranslationY(0);
- }
- mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
- }
-
- @Override
- public List<String> demoCommands() {
- ArrayList<String> commands = new ArrayList<>();
- commands.add(DemoMode.COMMAND_NOTIFICATIONS);
- return commands;
- }
-
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- if (mNotificationIconArea != null) {
- String visible = args.getString("visible");
- int vis = "false".equals(visible) ? View.INVISIBLE : View.VISIBLE;
- mNotificationIconArea.setVisibility(vis);
- }
- }
-
- @Override
- public void onDemoModeFinished() {
- if (mNotificationIconArea != null) {
- mNotificationIconArea.setVisibility(View.VISIBLE);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
deleted file mode 100644
index 4385a2e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.phone
-
-import android.content.Context
-import android.graphics.Rect
-import android.view.View
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.collection.ListEntry
-
-/**
- * A controller for the space in the status bar to the left of the system icons. This area is
- * normally reserved for notifications.
- */
-interface NotificationIconAreaController {
- /** Called by the Keyguard*ViewController whose view contains the aod icons. */
- fun setupAodIcons(aodIcons: NotificationIconContainer?)
- fun setShelfIcons(icons: NotificationIconContainer)
- fun onDensityOrFontScaleChanged(context: Context)
-
- /** Returns the view that represents the notification area. */
- fun getNotificationInnerAreaView(): View?
-
- /** Updates the notifications with the given list of notifications to display. */
- fun updateNotificationIcons(entries: List<@JvmSuppressWildcards ListEntry>)
- fun updateAodNotificationIcons()
- fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean)
- fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean)
- fun setAnimationsEnabled(enabled: Boolean)
- fun onThemeChanged()
- fun getHeight(): Int
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
deleted file mode 100644
index ba69370..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerModule.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.phone
-
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
-import dagger.Module
-import dagger.Provides
-import javax.inject.Provider
-
-@Module
-object NotificationIconAreaControllerModule {
- @Provides
- fun provideNotificationIconAreaControllerImpl(
- legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
- newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
- ): NotificationIconAreaController =
- if (NotificationIconContainerRefactor.isEnabled) {
- newProvider.get()
- } else {
- legacyProvider.get()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 8e3d678..ecd62bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -26,7 +26,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
-import android.util.MathUtils;
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.View;
@@ -42,7 +41,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -243,7 +241,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int childCount = getChildCount();
- final int maxVisibleIcons = getMaxVisibleIcons(childCount);
+ final int maxVisibleIcons = mMaxIcons;
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd());
@@ -284,22 +282,13 @@
@Override
public String toString() {
- if (NotificationIconContainerRefactor.isEnabled()) {
- return super.toString()
- + " {"
- + " overrideIconColor=" + mOverrideIconColor
- + ", maxIcons=" + mMaxIcons
- + ", isStaticLayout=" + mIsStaticLayout
- + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
- + " }";
- } else {
- return "NotificationIconContainer("
- + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
- + " overrideIconColor=" + mOverrideIconColor
- + " speedBumpIndex=" + mSpeedBumpIndex
- + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
- + ')';
- }
+ return super.toString()
+ + " {"
+ + " overrideIconColor=" + mOverrideIconColor
+ + ", maxIcons=" + mMaxIcons
+ + ", isStaticLayout=" + mIsStaticLayout
+ + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + " }";
}
@VisibleForTesting
@@ -349,13 +338,8 @@
}
}
if (child instanceof StatusBarIconView) {
- if (NotificationIconContainerRefactor.isEnabled()) {
- if (!mChangingViewPositions) {
- ((StatusBarIconView) child).updateIconDimens();
- }
- } else {
+ if (!mChangingViewPositions) {
((StatusBarIconView) child).updateIconDimens();
- ((StatusBarIconView) child).setDozing(mDozing, false, 0);
}
}
}
@@ -367,23 +351,11 @@
StatusBarIconView iconView = (StatusBarIconView) child;
Icon sourceIcon = iconView.getSourceIcon();
String groupKey = iconView.getNotification().getGroupKey();
- if (NotificationIconContainerRefactor.isEnabled()) {
- if (mReplacingIcons == null) {
- return false;
- }
- StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
- return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
- } else {
- if (mReplacingIconsLegacy == null) {
- return false;
- }
- ArrayList<StatusBarIcon> statusBarIcons = mReplacingIconsLegacy.get(groupKey);
- if (statusBarIcons != null) {
- StatusBarIcon replacedIcon = statusBarIcons.get(0);
- return sourceIcon.sameAs(replacedIcon.icon);
- }
+ if (mReplacingIcons == null) {
return false;
}
+ StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
+ return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
}
@Override
@@ -468,24 +440,14 @@
if (numIcons == 0) {
return 0f;
}
- final float contentWidth;
- if (NotificationIconContainerRefactor.isEnabled()) {
- contentWidth = mIconSize * numIcons;
- } else {
- contentWidth = mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1);
- }
+ final float contentWidth = mIconSize * numIcons;
return getActualPaddingStart() + contentWidth + getActualPaddingEnd();
}
@VisibleForTesting
boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
int maxVisibleIcons) {
- if (NotificationIconContainerRefactor.isEnabled()) {
- return i >= maxVisibleIcons && iconAppearAmount > 0.0f;
- } else {
- return speedBumpIndex != -1 && i >= speedBumpIndex
- && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
- }
+ return i >= maxVisibleIcons && iconAppearAmount > 0.0f;
}
@VisibleForTesting
@@ -510,7 +472,7 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = getMaxVisibleIcons(childCount);
+ int maxVisibleIcons = mMaxIcons;
float layoutEnd = getLayoutEnd();
mVisualOverflowStart = 0;
mFirstVisibleIconState = null;
@@ -592,27 +554,15 @@
}
private float getDrawingScale(View view) {
- final boolean useIncreasedScale = NotificationIconContainerRefactor.isEnabled()
- ? mUseIncreasedIconScale
- : mOnLockScreen;
- return useIncreasedScale && view instanceof StatusBarIconView
+ return mUseIncreasedIconScale && view instanceof StatusBarIconView
? ((StatusBarIconView) view).getIconScaleIncreased()
: 1f;
}
public void setUseIncreasedIconScale(boolean useIncreasedIconScale) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mUseIncreasedIconScale = useIncreasedIconScale;
}
- private int getMaxVisibleIcons(int childCount) {
- if (NotificationIconContainerRefactor.isEnabled()) {
- return mMaxIcons;
- } else {
- return mOnLockScreen ? mMaxIconsOnAod : mIsStaticLayout ? mMaxStaticIcons : childCount;
- }
- }
-
private float getLayoutEnd() {
return getActualWidth() - getActualPaddingEnd();
}
@@ -689,50 +639,11 @@
mChangingViewPositions = changingViewPositions;
}
- public void setDozing(boolean dozing, boolean animate, long delay) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- setDozing(dozing, animate, delay, /* endRunnable= */ null);
- }
-
- private void setDozing(boolean dozing, boolean animate, long delay,
- @Nullable Runnable endRunnable) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mDozing = dozing;
- mDisallowNextAnimation |= !animate;
- final int childCount = getChildCount();
- // Track all the child invocations of setDozing, invoking the top-level endRunnable once
- // they have all completed.
- final Runnable onChildCompleted = endRunnable == null ? null : new Runnable() {
- private int mPendingCallbacks = childCount;
-
- @Override
- public void run() {
- if (--mPendingCallbacks == 0) {
- endRunnable.run();
- }
- }
- };
- for (int i = 0; i < childCount; i++) {
- View view = getChildAt(i);
- if (view instanceof StatusBarIconView) {
- ((StatusBarIconView) view).setDozing(dozing, animate, delay, onChildCompleted);
- } else if (onChildCompleted != null) {
- onChildCompleted.run();
- }
- }
- }
-
public IconState getIconState(StatusBarIconView icon) {
return mIconStates.get(icon);
}
- public void setSpeedBumpIndex(int speedBumpIndex) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mSpeedBumpIndex = speedBumpIndex;
- }
-
public void setMaxIconsAmount(int maxIcons) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mMaxIcons = maxIcons;
}
@@ -754,36 +665,18 @@
mAnimationsEnabled = enabled;
}
- public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mReplacingIconsLegacy = replacingIcons;
- }
-
public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mReplacingIcons = replacingIcons;
}
- @Deprecated
- public void showIconIsolatedLegacy(StatusBarIconView icon, boolean animated) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- if (animated) {
- mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
- }
- mIsolatedIcon = icon;
- updateState();
- }
-
public void showIconIsolatedAnimated(StatusBarIconView icon,
@Nullable Runnable onAnimationEnd) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
mIsolatedIconAnimationEndRunnable = onAnimationEnd;
showIconIsolated(icon);
}
public void showIconIsolated(StatusBarIconView icon) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mIsolatedIcon = icon;
updateState();
}
@@ -795,23 +688,7 @@
}
}
- /**
- * Set whether the device is on the lockscreen and which lockscreen mode the device is
- * configured to. Depending on these values, the layout of the AOD icons change.
- */
- public void setOnLockScreen(boolean onLockScreen) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mOnLockScreen = onLockScreen;
- }
-
- @Deprecated
- public void setInNotificationIconShelf(boolean inShelf) {
- NotificationIconContainerRefactor.assertInLegacyMode();
- mOverrideIconColor = inShelf;
- }
-
public void setOverrideIconColor(boolean override) {
- if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
mOverrideIconColor = override;
}
@@ -922,14 +799,9 @@
}
}
icon.setVisibleState(visibleState, animationsAllowed);
- if (NotificationIconContainerRefactor.isEnabled()) {
- if (mOverrideIconColor) {
- icon.setIconColor(mThemedTextColorPrimary,
- /* animate= */ needsCannedAnimation && animationsAllowed);
- }
- } else {
- icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
- needsCannedAnimation && animationsAllowed);
+ if (mOverrideIconColor) {
+ icon.setIconColor(mThemedTextColorPrimary,
+ /* animate= */ needsCannedAnimation && animationsAllowed);
}
if (animate) {
animateTo(icon, animationProperties);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 2d775b7..f8f9b77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -708,7 +708,7 @@
* Shows the notification keyguard or the bouncer depending on
* {@link #needsFullscreenBouncer()}.
*/
- protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
+ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
boolean isDozing = mDozing;
if (Flags.simPinRaceConditionOnRestart()) {
KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
@@ -734,8 +734,11 @@
mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
}
}
- } else {
- Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
+ } else if (!isFalsingReset) {
+ // Falsing resets can cause this to flicker, so don't reset in this case
+ Log.i(TAG, "Sim bouncer is already showing, issuing a refresh");
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+
}
} else {
mCentralSurfaces.showKeyguard();
@@ -957,6 +960,10 @@
@Override
public void reset(boolean hideBouncerWhenShowing) {
+ reset(hideBouncerWhenShowing, /* isFalsingReset= */false);
+ }
+
+ public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
final boolean isOccluded = mKeyguardStateController.isOccluded();
// Hide quick settings.
@@ -968,7 +975,7 @@
hideBouncer(false /* destroyView */);
}
} else {
- showBouncerOrKeyguard(hideBouncerWhenShowing);
+ showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
}
if (hideBouncerWhenShowing) {
hideAlternateBouncer(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 07c190d..5be4ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -59,8 +59,6 @@
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
@@ -140,7 +138,6 @@
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
- private final NotificationIconAreaController mNotificationIconAreaController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
private final CarrierConfigTracker mCarrierConfigTracker;
@@ -237,7 +234,6 @@
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
- NotificationIconAreaController notificationIconAreaController,
ShadeExpansionStateManager shadeExpansionStateManager,
StatusBarIconController statusBarIconController,
DarkIconManager.Factory darkIconManagerFactory,
@@ -262,7 +258,6 @@
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mLocationPublisher = locationPublisher;
- mNotificationIconAreaController = notificationIconAreaController;
mShadeExpansionStateManager = shadeExpansionStateManager;
mStatusBarIconController = statusBarIconController;
mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
@@ -313,18 +308,14 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
- if (NotificationIconContainerRefactor.isEnabled()) {
- mDemoModeController.addCallback(mDemoModeCallback);
- }
+ mDemoModeController.addCallback(mDemoModeCallback);
}
@Override
public void onDestroy() {
super.onDestroy();
mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
- if (NotificationIconContainerRefactor.isEnabled()) {
- mDemoModeController.removeCallback(mDemoModeCallback);
- }
+ mDemoModeController.removeCallback(mDemoModeCallback);
}
@Override
@@ -471,11 +462,9 @@
mStartableStates.put(startable, Startable.State.STOPPED);
}
mDumpManager.unregisterDumpable(getClass().getSimpleName());
- if (NotificationIconContainerRefactor.isEnabled()) {
- if (mNicBindingDisposable != null) {
- mNicBindingDisposable.dispose();
- mNicBindingDisposable = null;
- }
+ if (mNicBindingDisposable != null) {
+ mNicBindingDisposable.dispose();
+ mNicBindingDisposable = null;
}
}
@@ -483,22 +472,12 @@
public void initNotificationIconArea() {
Trace.beginSection("CollapsedStatusBarFragment#initNotifIconArea");
ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
- if (NotificationIconContainerRefactor.isEnabled()) {
- LayoutInflater.from(getContext())
- .inflate(R.layout.notification_icon_area, notificationIconArea, true);
- NotificationIconContainer notificationIcons =
- notificationIconArea.requireViewById(R.id.notificationIcons);
- mNotificationIconAreaInner = notificationIcons;
- mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
- } else {
- mNotificationIconAreaInner =
- mNotificationIconAreaController.getNotificationInnerAreaView();
- if (mNotificationIconAreaInner.getParent() != null) {
- ((ViewGroup) mNotificationIconAreaInner.getParent())
- .removeView(mNotificationIconAreaInner);
- }
- notificationIconArea.addView(mNotificationIconAreaInner);
- }
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+ NotificationIconContainer notificationIcons =
+ notificationIconArea.requireViewById(R.id.notificationIcons);
+ mNotificationIconAreaInner = notificationIcons;
+ mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index b7531b0..44b692f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -131,7 +131,7 @@
val on = context.resources.getString(R.string.zen_mode_on)
val off = context.resources.getString(R.string.zen_mode_off)
- return mode.rule.triggerDescription ?: if (mode.isActive) on else off
+ return mode.getDynamicDescription(context) ?: if (mode.isActive) on else off
}
private fun makeZenModeDialog(): Dialog {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 1b00ae2..5980e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -38,8 +38,8 @@
TutorialScreenConfig.Strings(
titleResId = R.string.touchpad_back_gesture_action_title,
bodyResId = R.string.touchpad_back_gesture_guidance,
- titleSuccessResId = R.string.touchpad_tutorial_gesture_done,
- bodySuccessResId = R.string.touchpad_back_gesture_finished
+ titleSuccessResId = R.string.touchpad_back_gesture_success_title,
+ bodySuccessResId = R.string.touchpad_back_gesture_success_body
),
animations =
TutorialScreenConfig.Animations(
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 416c562..9ac2cba 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -213,10 +213,14 @@
transitionSpec = {
if (initialState == NOT_STARTED && targetState == IN_PROGRESS) {
val transitionDurationMillis = 150
- fadeIn(
- animationSpec = tween(transitionDurationMillis, easing = LinearEasing)
- ) togetherWith
- fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis))
+ fadeIn(animationSpec = tween(transitionDurationMillis, easing = LinearEasing))
+ .togetherWith(
+ fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis))
+ )
+ // we explicitly don't want size transform because when targetState
+ // animation is loaded for the first time, AnimatedContent thinks target
+ // size is smaller and tries to shrink initial state animation
+ .using(sizeTransform = null)
} else {
// empty transition works because all remaining transitions are from IN_PROGRESS
// state which shares initial animation frame with both FINISHED and NOT_STARTED
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
new file mode 100644
index 0000000..ed3110c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.touchpad.tutorial.ui.composable
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
+
+@Composable
+fun HomeGestureTutorialScreen(
+ onDoneButtonClicked: () -> Unit,
+ onBack: () -> Unit,
+) {
+ val screenConfig =
+ TutorialScreenConfig(
+ colors = rememberScreenColors(),
+ strings =
+ TutorialScreenConfig.Strings(
+ titleResId = R.string.touchpad_home_gesture_action_title,
+ bodyResId = R.string.touchpad_home_gesture_guidance,
+ titleSuccessResId = R.string.touchpad_home_gesture_success_title,
+ bodySuccessResId = R.string.touchpad_home_gesture_success_body
+ ),
+ animations =
+ TutorialScreenConfig.Animations(
+ educationResId = R.raw.trackpad_home_edu,
+ successResId = R.raw.trackpad_home_success
+ )
+ )
+ val gestureMonitorProvider =
+ object : GestureMonitorProvider {
+ override fun createGestureMonitor(
+ gestureDistanceThresholdPx: Int,
+ gestureStateChangedCallback: (GestureState) -> Unit
+ ): TouchpadGestureMonitor {
+ return HomeGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback)
+ }
+ }
+ GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
+}
+
+@Composable
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
+ val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim
+ val onPrimaryFixed = LocalAndroidColorScheme.current.onPrimaryFixed
+ val onPrimaryFixedVariant = LocalAndroidColorScheme.current.onPrimaryFixedVariant
+ val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
+ val dynamicProperties =
+ rememberLottieDynamicProperties(
+ rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
+ rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed),
+ rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
+ )
+ val screenColors =
+ remember(surfaceContainer, dynamicProperties) {
+ TutorialScreenConfig.Colors(
+ background = onPrimaryFixed,
+ successBackground = surfaceContainer,
+ title = primaryFixedDim,
+ animationColors = dynamicProperties,
+ )
+ }
+ return screenColors
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index 088a8fd..ad8ab30 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -28,6 +28,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
+import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.TutorialSelectionScreen
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE
import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE
@@ -78,6 +79,10 @@
onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
onBack = { vm.goTo(TUTORIAL_SELECTION) },
)
- HOME_GESTURE -> {}
+ HOME_GESTURE ->
+ HomeGestureTutorialScreen(
+ onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) },
+ onBack = { vm.goTo(TUTORIAL_SELECTION) },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 28ac2c0..055671c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,6 +28,7 @@
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -62,7 +63,9 @@
/**
* Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
* [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
- * during onViewAttached() and removing during onViewRemoved()
+ * during onViewAttached() and removing during onViewRemoved().
+ *
+ * @return a disposable handle in order to cancel the flow in the future.
*/
@JvmOverloads
fun <T> collectFlow(
@@ -71,8 +74,8 @@
consumer: Consumer<T>,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
state: Lifecycle.State = Lifecycle.State.CREATED,
-) {
- view.repeatWhenAttached(coroutineContext) {
+): DisposableHandle {
+ return view.repeatWhenAttached(coroutineContext) {
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 816f55d..7fcabe4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -42,28 +42,31 @@
mBgDispatcher = bgDispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Settings.Global.getUriFor(name);
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mBgDispatcher;
}
@Override
- public String getString(String name) {
+ public String getString(@NonNull String name) {
return Settings.Global.getString(mContentResolver, name);
}
@Override
- public boolean putString(String name, String value) {
+ public boolean putString(@NonNull String name, String value) {
return Settings.Global.putString(mContentResolver, name, value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index f1da27f..c296481 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -16,12 +16,11 @@
package com.android.systemui.util.settings;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.Settings;
-import androidx.annotation.NonNull;
-
import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -43,46 +42,50 @@
mBgDispatcher = bgDispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
+ @NonNull
@Override
public CurrentUserIdProvider getCurrentUserProvider() {
return mCurrentUserProvider;
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Settings.Secure.getUriFor(name);
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mBgDispatcher;
}
@Override
- public String getStringForUser(String name, int userHandle) {
+ public String getStringForUser(@NonNull String name, int userHandle) {
return Settings.Secure.getStringForUser(mContentResolver, name,
getRealUserHandle(userHandle));
}
@Override
- public boolean putString(String name, String value, boolean overrideableByRestore) {
+ public boolean putString(@NonNull String name, String value, boolean overrideableByRestore) {
return Settings.Secure.putString(mContentResolver, name, value, overrideableByRestore);
}
@Override
- public boolean putStringForUser(String name, String value, int userHandle) {
+ public boolean putStringForUser(@NonNull String name, String value, int userHandle) {
return Settings.Secure.putStringForUser(mContentResolver, name, value,
getRealUserHandle(userHandle));
}
@Override
- public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
- int userHandle, boolean overrideableByRestore) {
+ public boolean putStringForUser(@NonNull String name, String value, String tag,
+ boolean makeDefault, int userHandle, boolean overrideableByRestore) {
return Settings.Secure.putStringForUser(
mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
overrideableByRestore);
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 0ee997e..82f41a7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -346,7 +346,7 @@
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- fun putString(name: String, value: String): Boolean
+ fun putString(name: String, value: String?): Boolean
/**
* Store a name/value pair into the database.
@@ -377,7 +377,7 @@
* @return true if the value was set, false on database errors.
* @see .resetToDefaults
*/
- fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
+ fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean
/**
* Convenience function for retrieving a single secure settings value as an integer. Note that
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 1e80357..e670b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -16,12 +16,11 @@
package com.android.systemui.util.settings;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.Settings;
-import androidx.annotation.NonNull;
-
import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -42,46 +41,50 @@
mBgCoroutineDispatcher = bgDispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
+ @NonNull
@Override
public CurrentUserIdProvider getCurrentUserProvider() {
return mCurrentUserProvider;
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Settings.System.getUriFor(name);
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mBgCoroutineDispatcher;
}
@Override
- public String getStringForUser(String name, int userHandle) {
+ public String getStringForUser(@NonNull String name, int userHandle) {
return Settings.System.getStringForUser(mContentResolver, name,
getRealUserHandle(userHandle));
}
@Override
- public boolean putString(String name, String value, boolean overrideableByRestore) {
+ public boolean putString(@NonNull String name, String value, boolean overrideableByRestore) {
return Settings.System.putString(mContentResolver, name, value, overrideableByRestore);
}
@Override
- public boolean putStringForUser(String name, String value, int userHandle) {
+ public boolean putStringForUser(@NonNull String name, String value, int userHandle) {
return Settings.System.putStringForUser(mContentResolver, name, value,
getRealUserHandle(userHandle));
}
@Override
- public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
- int userHandle, boolean overrideableByRestore) {
+ public boolean putStringForUser(@NonNull String name, String value, String tag,
+ boolean makeDefault, int userHandle, boolean overrideableByRestore) {
throw new UnsupportedOperationException(
"This method only exists publicly for Settings.Secure and Settings.Global");
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 9ae8f03..8e3b813 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -368,19 +368,19 @@
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean
+ fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean
- override fun putString(name: String, value: String): Boolean {
+ override fun putString(name: String, value: String?): Boolean {
return putStringForUser(name, value, userId)
}
/** Similar implementation to [putString] for the specified [userHandle]. */
- fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
+ fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean
/** Similar implementation to [putString] for the specified [userHandle]. */
fun putStringForUser(
name: String,
- value: String,
+ value: String?,
tag: String?,
makeDefault: Boolean,
@UserIdInt userHandle: Int,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 319b615..2bb9e68 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -55,7 +55,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerAlwaysOnDisplayViewBinder;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
@@ -78,8 +77,6 @@
@Mock
KeyguardSliceViewController mKeyguardSliceViewController;
@Mock
- NotificationIconAreaController mNotificationIconAreaController;
- @Mock
LockscreenSmartspaceController mSmartspaceController;
@Mock
@@ -176,7 +173,6 @@
mStatusBarStateController,
mClockRegistry,
mKeyguardSliceViewController,
- mNotificationIconAreaController,
mSmartspaceController,
mock(NotificationIconContainerAlwaysOnDisplayViewBinder.class),
mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 16b9ab5..ff47fd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -48,6 +48,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
@@ -102,6 +103,8 @@
private AccessibilityLogger mA11yLogger;
@Mock
private IWindowManager mIWindowManager;
+ @Mock
+ private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private IMagnificationConnection mIMagnificationConnection;
private MagnificationImpl mMagnification;
@@ -123,7 +126,8 @@
mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class),
- mA11yLogger, mIWindowManager, mAccessibilityManager);
+ mA11yLogger, mIWindowManager, mAccessibilityManager,
+ mViewCaptureAwareWindowManager);
mMagnification.mWindowMagnificationControllerSupplier =
new FakeWindowMagnificationControllerSupplier(
mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 5be1180..1ceac78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -73,10 +73,14 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
+import kotlin.Lazy;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -104,6 +108,8 @@
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
private MagnificationModeSwitch.ClickListener mClickListener;
+ @Mock
+ private Lazy<ViewCapture> mLazyViewCapture;
private TestableWindowManager mWindowManager;
private ViewPropertyAnimator mViewPropertyAnimator;
private MagnificationModeSwitch mMagnificationModeSwitch;
@@ -133,8 +139,10 @@
return null;
}).when(mSfVsyncFrameProvider).postFrameCallback(
any(Choreographer.FrameCallback.class));
+ ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
+ mLazyViewCapture, false);
mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView,
- mSfVsyncFrameProvider, mClickListener);
+ mSfVsyncFrameProvider, mClickListener, vwm);
assertNotNull(mTouchListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index d0f8e78..3cd3fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
@@ -56,6 +57,8 @@
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
@Before
public void setUp() {
@@ -63,7 +66,7 @@
mMagnificationSettingsController = new MagnificationSettingsController(
mContext, mSfVsyncFrameProvider,
mMagnificationSettingControllerCallback, mSecureSettings,
- mWindowMagnificationSettings);
+ mWindowMagnificationSettings, mViewCaptureAwareWindowManager);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 038b81b..057ddcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -50,6 +50,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
@@ -96,6 +97,8 @@
private AccessibilityLogger mA11yLogger;
@Mock
private IWindowManager mIWindowManager;
+ @Mock
+ private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
@Before
public void setUp() throws Exception {
@@ -129,7 +132,8 @@
mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
- getContext().getSystemService(AccessibilityManager.class));
+ getContext().getSystemService(AccessibilityManager.class),
+ mViewCaptureAwareWindowManager);
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index 6e94297..e1e515e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -28,6 +28,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
@@ -50,6 +51,8 @@
private View mSpyView;
@Mock
private MagnificationModeSwitch.ClickListener mListener;
+ @Mock
+ private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
@Before
@@ -58,7 +61,8 @@
mSupplier = new FakeSwitchSupplier(mContext.getSystemService(DisplayManager.class));
mModeSwitchesController = new ModeSwitchesController(mSupplier);
mModeSwitchesController.setClickListenerDelegate(mListener);
- mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+ mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController,
+ mViewCaptureAwareWindowManager));
mSpyView = Mockito.spy(new View(mContext));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 003f7e4..9507077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -61,6 +61,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
@@ -68,6 +70,8 @@
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Lazy;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -95,6 +99,8 @@
private SecureSettings mSecureSettings;
@Mock
private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+ @Mock
+ private Lazy<ViewCapture> mLazyViewCapture;
private TestableWindowManager mWindowManager;
private WindowMagnificationSettings mWindowMagnificationSettings;
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
@@ -119,9 +125,11 @@
when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
returnsSecondArg());
+ ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
+ mLazyViewCapture, /* isViewCaptureEnabled= */ false);
mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider,
- mSecureSettings);
+ mSecureSettings, vwm);
mSettingView = mWindowMagnificationSettings.getSettingView();
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 5600b87..a18d272 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -711,6 +711,16 @@
}
@Test
+ public void testDestroy_cleansUpHandler() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+ environment.destroyMonitor();
+ verify(touchHandler).onDestroy();
+ }
+
+ @Test
public void testLastSessionPop_createsNewInputSession() {
final TouchHandler touchHandler = createTouchHandler();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 512d946..534f25c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -503,11 +503,13 @@
collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
val shouldAnimateIconView by
collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
+ val message by collectLastValue(kosmos.promptViewModel.message)
verifyIconSize()
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
+ dismissAfterDelay = DELAY,
+ "TEST"
)
if (testCase.isFingerprintOnly) {
@@ -531,6 +533,7 @@
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(message).isEqualTo(PromptMessage.Empty)
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 8246506..74bc928 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -208,8 +208,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.connectableProfiles)
- .thenReturn(listOf(leAudioProfile))
+ whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+ .thenReturn(listOf(leAudioProfile))
whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
@@ -243,8 +243,8 @@
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.connectableProfiles)
- .thenReturn(listOf(leAudioProfile))
+ whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+ .thenReturn(listOf(leAudioProfile))
whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
@@ -254,12 +254,12 @@
whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
+ BluetoothUtils.hasConnectedBroadcastSource(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
)
- )
- .thenReturn(false)
+ .thenReturn(false)
actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
verify(activityStarter)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 79e312f..77337d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -21,8 +21,12 @@
import android.hardware.fingerprint.FingerprintManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
@@ -41,6 +45,7 @@
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -65,6 +70,7 @@
private val bouncerRepository = kosmos.keyguardBouncerRepository
private val powerRepository = kosmos.fakePowerRepository
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val communalSceneRepository = kosmos.communalSceneRepository
private val mockedContext = kosmos.mockedContext
private val mockedActivityStarter = kosmos.activityStarter
@@ -143,6 +149,20 @@
}
@Test
+ fun lockout_onOccludingApp_onCommunal_neverGoToHomeScreen() =
+ testScope.runTest {
+ givenOnOccludingApp(isOnOccludingApp = true, isOnCommunal = true)
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ "lockoutTest"
+ )
+ )
+ runCurrent()
+ verifyNeverGoToHomeScreen()
+ }
+
+ @Test
fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
testScope.runTest {
val message by collectLastValue(underTest.message)
@@ -261,7 +281,10 @@
assertThat(message).isNull()
}
- private suspend fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+ private suspend fun givenOnOccludingApp(
+ isOnOccludingApp: Boolean,
+ isOnCommunal: Boolean = false
+ ) {
powerRepository.setInteractive(true)
keyguardRepository.setIsDozing(false)
keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
@@ -270,6 +293,14 @@
bouncerRepository.setPrimaryShow(!isOnOccludingApp)
bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+ kosmos.fakeCommunalSceneRepository.setTransitionState(
+ flowOf(
+ ObservableTransitionState.Idle(
+ if (isOnCommunal) CommunalScenes.Communal else CommunalScenes.Blank
+ )
+ )
+ )
+
if (isOnOccludingApp) {
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 20cb1e1..0ac04b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -456,8 +457,20 @@
assertThat(value?.ids()).containsExactly(DEFAULT_DISPLAY)
}
+ @Test
+ fun displayFlow_emitsCorrectDisplaysAtFirst() =
+ testScope.runTest {
+ setDisplays(0, 1, 2)
+
+ val values: List<Set<Display>> by collectValues(displayRepository.displays)
+
+ assertThat(values.toIdSets()).containsExactly(setOf(0, 1, 2))
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
+ private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
+
// Wrapper to capture the displayListener.
private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
val flowValue = collectLastValue(displayRepository.displays)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index e44bc7b..313292a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -102,34 +102,6 @@
}
@Test
- fun forcePluginOpen() =
- testScope.runTest {
- val forcePluginOpen by collectLastValue(underTest.forcePluginOpen)
-
- transitionRepository.sendTransitionSteps(
- listOf(
- stepToAlternateBouncer(0f, TransitionState.STARTED),
- stepToAlternateBouncer(.4f),
- stepToAlternateBouncer(.6f),
- stepToAlternateBouncer(1f),
- ),
- testScope,
- )
- assertThat(forcePluginOpen).isTrue()
-
- transitionRepository.sendTransitionSteps(
- listOf(
- stepFromAlternateBouncer(0f, TransitionState.STARTED),
- stepFromAlternateBouncer(.3f),
- stepFromAlternateBouncer(.6f),
- stepFromAlternateBouncer(1f),
- ),
- testScope,
- )
- assertThat(forcePluginOpen).isFalse()
- }
-
- @Test
fun registerForDismissGestures() =
testScope.runTest {
val registerForDismissGestures by collectLastValue(underTest.registerForDismissGestures)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 77977f3..24bea2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -195,9 +195,7 @@
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
val featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
- }
+ FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
keyguardInteractor = withDeps.keyguardInteractor
@@ -289,6 +287,7 @@
underTest =
KeyguardQuickAffordancesCombinedViewModel(
+ applicationScope = testScope.backgroundScope,
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
index a73df07..9797c8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -104,6 +104,7 @@
WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
/* appearance= */ 0,
/* isTranslucent= */ false,
- /* hasImeSurface= */ false
+ /* hasImeSurface= */ false,
+ /* uiMode */ 0
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index a8cbbd4..a52ab0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -20,7 +20,6 @@
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.inputmethodservice.InputMethodService.IME_VISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -512,7 +511,7 @@
externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE,
BACK_DISPOSITION_DEFAULT, true);
- defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_INVISIBLE,
+ defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */,
BACK_DISPOSITION_DEFAULT, false);
// Verify IME window state will be updated in external NavBar & default NavBar state reset.
assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
index 90e0dd8..0c2b59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
@@ -17,25 +17,34 @@
package com.android.systemui.qs
import android.os.Handler
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private typealias Callback = (Int, Boolean) -> Unit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper
-class UserSettingObserverTest : SysuiTestCase() {
+class UserSettingObserverTest(flags: FlagsParameterization) : SysuiTestCase() {
companion object {
private const val TEST_SETTING = "setting"
@@ -43,8 +52,23 @@
private const val OTHER_USER = 1
private const val DEFAULT_VALUE = 1
private val FAIL_CALLBACK: Callback = { _, _ -> fail("Callback should not be called") }
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_QS_REGISTER_SETTING_OBSERVER_ON_BG_THREAD
+ )
+ }
}
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
private lateinit var testableLooper: TestableLooper
private lateinit var setting: UserSettingObserver
private lateinit var secureSettings: SecureSettings
@@ -54,7 +78,7 @@
@Before
fun setUp() {
testableLooper = TestableLooper.get(this)
- secureSettings = FakeSettings()
+ secureSettings = kosmos.fakeSettings
setting =
object :
@@ -76,92 +100,107 @@
@After
fun tearDown() {
- setting.isListening = false
+ setListening(false)
}
@Test
- fun testNotListeningByDefault() {
- callback = FAIL_CALLBACK
+ fun testNotListeningByDefault() =
+ testScope.runTest {
+ callback = FAIL_CALLBACK
- assertThat(setting.isListening).isFalse()
- secureSettings.putIntForUser(TEST_SETTING, 2, USER)
- testableLooper.processAllMessages()
- }
+ assertThat(setting.isListening).isFalse()
+ secureSettings.putIntForUser(TEST_SETTING, 2, USER)
+ testableLooper.processAllMessages()
+ }
@Test
- fun testChangedWhenListeningCallsCallback() {
- var changed = false
- callback = { _, _ -> changed = true }
+ fun testChangedWhenListeningCallsCallback() =
+ testScope.runTest {
+ var changed = false
+ callback = { _, _ -> changed = true }
- setting.isListening = true
- secureSettings.putIntForUser(TEST_SETTING, 2, USER)
- testableLooper.processAllMessages()
+ setListening(true)
+ secureSettings.putIntForUser(TEST_SETTING, 2, USER)
+ testableLooper.processAllMessages()
- assertThat(changed).isTrue()
- }
+ assertThat(changed).isTrue()
+ }
@Test
- fun testListensToCorrectSetting() {
- callback = FAIL_CALLBACK
+ fun testListensToCorrectSetting() =
+ testScope.runTest {
+ callback = FAIL_CALLBACK
- setting.isListening = true
- secureSettings.putIntForUser("other", 2, USER)
- testableLooper.processAllMessages()
- }
+ setListening(true)
+ secureSettings.putIntForUser("other", 2, USER)
+ testableLooper.processAllMessages()
+ }
@Test
- fun testGetCorrectValue() {
- secureSettings.putIntForUser(TEST_SETTING, 2, USER)
- assertThat(setting.value).isEqualTo(2)
+ fun testGetCorrectValue() =
+ testScope.runTest {
+ secureSettings.putIntForUser(TEST_SETTING, 2, USER)
+ assertThat(setting.value).isEqualTo(2)
- secureSettings.putIntForUser(TEST_SETTING, 4, USER)
- assertThat(setting.value).isEqualTo(4)
- }
+ secureSettings.putIntForUser(TEST_SETTING, 4, USER)
+ assertThat(setting.value).isEqualTo(4)
+ }
@Test
- fun testSetValue() {
- setting.value = 5
- assertThat(secureSettings.getIntForUser(TEST_SETTING, USER)).isEqualTo(5)
- }
+ fun testSetValue() =
+ testScope.runTest {
+ setting.value = 5
+ assertThat(secureSettings.getIntForUser(TEST_SETTING, USER)).isEqualTo(5)
+ }
@Test
- fun testChangeUser() {
- setting.isListening = true
- setting.setUserId(OTHER_USER)
+ fun testChangeUser() =
+ testScope.runTest {
+ setListening(true)
+ setting.setUserId(OTHER_USER)
- setting.isListening = true
- assertThat(setting.currentUser).isEqualTo(OTHER_USER)
- }
+ setListening(true)
+ assertThat(setting.currentUser).isEqualTo(OTHER_USER)
+ }
@Test
- fun testDoesntListenInOtherUsers() {
- callback = FAIL_CALLBACK
- setting.isListening = true
+ fun testDoesntListenInOtherUsers() =
+ testScope.runTest {
+ callback = FAIL_CALLBACK
+ setListening(true)
- secureSettings.putIntForUser(TEST_SETTING, 3, OTHER_USER)
- testableLooper.processAllMessages()
- }
+ secureSettings.putIntForUser(TEST_SETTING, 3, OTHER_USER)
+ testableLooper.processAllMessages()
+ }
@Test
- fun testListensToCorrectUserAfterChange() {
- var changed = false
- callback = { _, _ -> changed = true }
+ fun testListensToCorrectUserAfterChange() =
+ testScope.runTest {
+ var changed = false
+ callback = { _, _ -> changed = true }
- setting.isListening = true
- setting.setUserId(OTHER_USER)
- secureSettings.putIntForUser(TEST_SETTING, 2, OTHER_USER)
- testableLooper.processAllMessages()
+ setListening(true)
+ setting.setUserId(OTHER_USER)
+ testScope.runCurrent()
+ secureSettings.putIntForUser(TEST_SETTING, 2, OTHER_USER)
+ testableLooper.processAllMessages()
- assertThat(changed).isTrue()
- }
+ assertThat(changed).isTrue()
+ }
@Test
- fun testDefaultValue() {
- // Check default value before listening
- assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+ fun testDefaultValue() =
+ testScope.runTest {
+ // Check default value before listening
+ assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
- // Check default value if setting is not set
- setting.isListening = true
- assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+ // Check default value if setting is not set
+ setListening(true)
+ assertThat(setting.value).isEqualTo(DEFAULT_VALUE)
+ }
+
+ fun setListening(listening: Boolean) {
+ setting.isListening = listening
+ testScope.runCurrent()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index f866f74..3f550ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,8 +16,13 @@
package com.android.systemui.screenrecord;
+import static com.android.systemui.screenrecord.RecordingService.GROUP_KEY_ERROR_SAVING;
+import static com.android.systemui.screenrecord.RecordingService.GROUP_KEY_SAVED;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,6 +30,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -73,8 +79,6 @@
@Mock
private ScreenMediaRecorder mScreenMediaRecorder;
@Mock
- private Notification mNotification;
- @Mock
private Executor mExecutor;
@Mock
private Handler mHandler;
@@ -124,9 +128,6 @@
// Mock notifications
doNothing().when(mRecordingService).createRecordingNotification();
- doReturn(mNotification).when(mRecordingService).createProcessingNotification();
- doReturn(mNotification).when(mRecordingService).createSaveNotification(any());
- doNothing().when(mRecordingService).createErrorNotification();
doNothing().when(mRecordingService).showErrorToast(anyInt());
doNothing().when(mRecordingService).stopForeground(anyInt());
@@ -227,6 +228,33 @@
}
@Test
+ public void testOnSystemRequestedStop_whenRecordingInProgress_showsNotifications() {
+ doReturn(true).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ // Processing notification
+ ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+ assertEquals(GROUP_KEY_SAVED, notifCaptor.getValue().getGroup());
+
+ reset(mNotificationManager);
+ verify(mExecutor).execute(mRunnableCaptor.capture());
+ mRunnableCaptor.getValue().run();
+
+ verify(mNotificationManager, times(2))
+ .notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+ // Saved notification
+ Notification saveNotification = notifCaptor.getAllValues().get(0);
+ assertFalse(saveNotification.isGroupSummary());
+ assertEquals(GROUP_KEY_SAVED, saveNotification.getGroup());
+ // Group summary notification
+ Notification groupSummaryNotification = notifCaptor.getAllValues().get(1);
+ assertTrue(groupSummaryNotification.isGroupSummary());
+ assertEquals(GROUP_KEY_SAVED, groupSummaryNotification.getGroup());
+ }
+
+ @Test
public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
throws IOException {
doReturn(true).when(mController).isRecording();
@@ -234,7 +262,11 @@
mRecordingService.onStopped();
- verify(mRecordingService).createErrorNotification();
+ verify(mRecordingService).createErrorSavingNotification(any());
+ ArgumentCaptor<Notification> notifCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notifyAsUser(any(), anyInt(), notifCaptor.capture(), any());
+ assertTrue(notifCaptor.getValue().isGroupSummary());
+ assertEquals(GROUP_KEY_ERROR_SAVING, notifCaptor.getValue().getGroup());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 2803035..8125ef5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -192,6 +192,7 @@
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -428,6 +429,9 @@
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
+ final SplitShadeStateController splitShadeStateController =
+ new ResourcesSplitShadeStateController();
+
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
mKosmos.getDeviceProvisioningInteractor(),
@@ -445,7 +449,8 @@
new SharedNotificationContainerInteractor(
new FakeConfigurationRepository(),
mContext,
- new ResourcesSplitShadeStateController(),
+ () -> splitShadeStateController,
+ () -> mShadeInteractor,
mKeyguardInteractor,
deviceEntryUdfpsInteractor,
() -> mLargeScreenHeaderHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 2c2fcbe..13bc82f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -28,7 +27,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
@@ -166,31 +164,7 @@
}
@Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
- val headerResourceHeight = 20
- val headerHelperHeight = 30
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
- .thenReturn(headerHelperHeight)
- overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.qs_header_height, 10)
- overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
-
- // ensure the estimated height (would be 3 here) wouldn't impact this test case
- overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
- overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
-
- underTest.updateResources()
-
- val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
- verify(view).applyConstraints(capture(captor))
- assertThat(captor.value.getHeight(R.id.split_shade_status_bar))
- .isEqualTo(headerResourceHeight)
- }
-
- @Test
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
+ fun testLargeScreen_updateResources_splitShadeHeightIsSetBasedOnHelper() {
val headerResourceHeight = 20
val headerHelperHeight = 30
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -447,31 +421,8 @@
}
@Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
- setLargeScreen()
- val largeScreenHeaderResourceHeight = 100
- val largeScreenHeaderHelperHeight = 200
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
- .thenReturn(largeScreenHeaderHelperHeight)
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
- // ensure the estimated height (would be 30 here) wouldn't impact this test case
- overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
- overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
- underTest.updateResources()
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderResourceHeight)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(largeScreenHeaderResourceHeight)
- }
-
- @Test
@DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index f21def3..4850b0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.shade
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -28,7 +27,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
@@ -164,29 +162,7 @@
}
@Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
- val helperHeight = 30
- val resourceHeight = 20
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
- overrideResource(R.bool.config_use_large_screen_shade_header, true)
- overrideResource(R.dimen.qs_header_height, 10)
- overrideResource(R.dimen.large_screen_shade_header_height, resourceHeight)
-
- // ensure the estimated height (would be 3 here) wouldn't impact this test case
- overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
- overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
-
- underTest.updateResources()
-
- val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
- verify(view).applyConstraints(capture(captor))
- assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(resourceHeight)
- }
-
- @Test
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
+ fun testLargeScreen_updateResources_splitShadeHeightIsSet_basedOnHelper() {
val helperHeight = 30
val resourceHeight = 20
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -427,28 +403,7 @@
}
@Test
- @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
- setLargeScreen()
- val largeScreenHeaderHelperHeight = 200
- val largeScreenHeaderResourceHeight = 100
- whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
- .thenReturn(largeScreenHeaderHelperHeight)
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderResourceHeight)
-
- // ensure the estimated height (would be 30 here) wouldn't impact this test case
- overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
- overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
-
- underTest.updateResources()
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderResourceHeight)
- }
-
- @Test
- @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
setLargeScreen()
val largeScreenHeaderHelperHeight = 200
val largeScreenHeaderResourceHeight = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index e57382d..505f799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -225,7 +225,8 @@
new SharedNotificationContainerInteractor(
configurationRepository,
mContext,
- splitShadeStateController,
+ () -> splitShadeStateController,
+ () -> mShadeInteractor,
keyguardInteractor,
deviceEntryUdfpsInteractor,
() -> mLargeScreenHeaderHelper),
@@ -266,6 +267,7 @@
when(mPanelView.getParent()).thenReturn(mPanelViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
+ when(mQSFragment.getHeader()).thenReturn(mQsHeader);
doAnswer(invocation -> {
mLockscreenShadeTransitionCallback = invocation.getArgument(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index e7db469..2e9d6e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -24,34 +24,41 @@
import static android.view.MotionEvent.BUTTON_SECONDARY;
import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
-import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR;
import static com.android.systemui.Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import java.util.List;
@@ -65,7 +72,7 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return progressionOf(FLAG_QS_UI_REFACTOR, FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT);
+ return progressionOf(FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT);
}
public QuickSettingsControllerImplTest(FlagsParameterization flags) {
@@ -244,6 +251,61 @@
}
@Test
+ @DisableFlags(QSComposeFragment.FLAG_NAME)
+ public void onQsFragmentAttached_qsComposeFragmentDisabled_setHeaderInNSSL() {
+ mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+ verify(mNotificationStackScrollLayoutController)
+ .setQsHeader((ViewGroup) mQSFragment.getHeader());
+ verify(mNotificationStackScrollLayoutController, never()).setQsHeaderBoundsProvider(any());
+ }
+
+ @Test
+ @EnableFlags(QSComposeFragment.FLAG_NAME)
+ public void onQsFragmentAttached_qsComposeFragmentEnabled_setQsHeaderBoundsProviderInNSSL() {
+ mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+ verify(mNotificationStackScrollLayoutController, never())
+ .setQsHeader((ViewGroup) mQSFragment.getHeader());
+ ArgumentCaptor<QSHeaderBoundsProvider> argumentCaptor =
+ ArgumentCaptor.forClass(QSHeaderBoundsProvider.class);
+
+ verify(mNotificationStackScrollLayoutController)
+ .setQsHeaderBoundsProvider(argumentCaptor.capture());
+
+ argumentCaptor.getValue().getLeftProvider().invoke();
+ argumentCaptor.getValue().getHeightProvider().invoke();
+ argumentCaptor.getValue().getBoundsOnScreenProvider().invoke(new Rect());
+ InOrder inOrderVerifier = inOrder(mQSFragment);
+
+ inOrderVerifier.verify(mQSFragment).getHeaderLeft();
+ inOrderVerifier.verify(mQSFragment).getHeaderHeight();
+ inOrderVerifier.verify(mQSFragment).getHeaderBoundsOnScreen(new Rect());
+ }
+
+ @Test
+ @DisableFlags(QSComposeFragment.FLAG_NAME)
+ public void onQSFragmentDetached_qsComposeFragmentFlagDisabled_setViewToNullInNSSL() {
+ mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+ mFragmentListener.onFragmentViewDestroyed(QS.TAG, mQSFragment);
+
+ verify(mNotificationStackScrollLayoutController).setQsHeader(null);
+ verify(mNotificationStackScrollLayoutController, never()).setQsHeaderBoundsProvider(null);
+ }
+
+ @Test
+ @EnableFlags(QSComposeFragment.FLAG_NAME)
+ public void onQSFragmentDetached_qsComposeFragmentFlagEnabled_setBoundsProviderToNullInNSSL() {
+ mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
+
+ mFragmentListener.onFragmentViewDestroyed(QS.TAG, mQSFragment);
+
+ verify(mNotificationStackScrollLayoutController, never()).setQsHeader(null);
+ verify(mNotificationStackScrollLayoutController).setQsHeaderBoundsProvider(null);
+ }
+
+ @Test
public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() {
setIsFullWidth(false);
mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 86d21e8..6916bbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -16,7 +16,6 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
-import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
@@ -207,7 +206,7 @@
mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, 1, 2, true);
waitForIdleSync();
- verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(IME_INVISIBLE),
+ verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(0),
eq(BACK_DISPOSITION_DEFAULT), eq(false));
verify(mCallbacks).setImeWindowStatus(eq(SECONDARY_DISPLAY), eq(1), eq(2), eq(true));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 80011dc..a75d7b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ADAPTIVE_AUTH;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -1535,6 +1536,48 @@
trustGrantedMsg);
}
+ @Test
+ public void updateAdaptiveAuthMessage_whenNotLockedByAdaptiveAuth_doesNotShowMsg() {
+ // When the device is not locked by adaptive auth
+ when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+ .thenReturn(false);
+ createController();
+ mController.setVisible(true);
+
+ // Verify that the adaptive auth message does not show
+ verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
+ }
+
+ @Test
+ public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_cannotSkipBouncer_showsMsg() {
+ // When the device is locked by adaptive auth, and the user cannot skip bouncer
+ when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(false);
+ createController();
+ mController.setVisible(true);
+
+ // Verify that the adaptive auth message shows
+ String message = mContext.getString(R.string.keyguard_indication_after_adaptive_auth_lock);
+ verifyIndicationMessage(INDICATION_TYPE_ADAPTIVE_AUTH, message);
+ }
+
+ @Test
+ public void updateAdaptiveAuthMessage_whenLockedByAdaptiveAuth_canSkipBouncer_doesNotShowMsg() {
+ createController();
+ mController.setVisible(true);
+
+ // When the device is locked by adaptive auth, but the device unlocked state changes and the
+ // user can skip bouncer
+ when(mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser())).thenReturn(true);
+ mKeyguardStateControllerCallback.onUnlockedChanged();
+
+ // Verify that the adaptive auth message does not show
+ verifyNoMessage(INDICATION_TYPE_ADAPTIVE_AUTH);
+ }
+
private void screenIsTurningOn() {
when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index ef1c927..27a1bb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationManager;
@@ -41,7 +40,6 @@
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -149,30 +147,4 @@
verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
verifyNoMoreInteractions(mNotificationHandler);
}
-
- @Test
- public void testOnConnectReadStatusBarSetting() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
- NotificationListener.NotificationSettingsListener settingsListener =
- mock(NotificationListener.NotificationSettingsListener.class);
- mListener.addNotificationSettingsListener(settingsListener);
-
- when(mNotificationManager.shouldHideSilentStatusBarIcons()).thenReturn(true);
-
- mListener.onListenerConnected();
-
- verify(settingsListener).onStatusBarIconsBehaviorChanged(true);
- }
-
- @Test
- public void testOnStatusBarIconsBehaviorChanged() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
- NotificationListener.NotificationSettingsListener settingsListener =
- mock(NotificationListener.NotificationSettingsListener.class);
- mListener.addNotificationSettingsListener(settingsListener);
-
- mListener.onSilentStatusBarIconsVisibilityChanged(true);
-
- verify(settingsListener).onStatusBarIconsBehaviorChanged(true);
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index e68fa0b..804eb5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -231,6 +231,34 @@
assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(5678)
}
+ /** Regression test for b/349620526. */
+ @Test
+ fun chip_recordingState_thenGetsTaskInfo_startTimeDoesNotChange() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ // Start recording, but without any task info
+ systemClock.setElapsedRealtime(1234)
+ screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+
+ // WHEN we receive the recording task info a few milliseconds later
+ systemClock.setElapsedRealtime(1240)
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.SingleTask(
+ "host.package",
+ hostDeviceName = null,
+ FakeActivityTaskManager.createTask(taskId = 1)
+ )
+
+ // THEN the start time is still the old start time
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs).isEqualTo(1234)
+ }
+
@Test
fun chip_notProjecting_clickListenerShowsDialog() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index c7513de..ad6aca1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -34,10 +34,8 @@
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
-import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
@@ -47,8 +45,8 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -61,10 +59,10 @@
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
- @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
@Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
@Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor
- @Mock private lateinit var sensitiveNotificationProtectionController:
+ @Mock
+ private lateinit var sensitiveNotificationProtectionController:
SensitiveNotificationProtectionController
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
@@ -73,14 +71,12 @@
fun setUp() {
initMocks(this)
- whenever(sensitiveNotificationProtectionController.isSensitiveStateActive)
- .thenReturn(false)
+ whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
entry = NotificationEntryBuilder().setSection(section).build()
coordinator =
StackCoordinator(
groupExpansionManagerImpl,
- notificationIconAreaController,
renderListInteractor,
activeNotificationsInteractor,
sensitiveNotificationProtectionController,
@@ -92,15 +88,7 @@
}
@Test
- @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
- fun testUpdateNotificationIcons() {
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
- }
-
- @Test
- @EnableFlags(NotificationIconContainerRefactor.FLAG_NAME)
- fun testSetRenderedListOnInteractor_iconContainerFlagOn() {
+ fun testSetRenderedListOnInteractor() {
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 3f28164..491919a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction
import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
@@ -78,7 +79,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
-@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME)
+@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME, LockscreenOtpRedaction.FLAG_NAME)
class NotificationRowContentBinderImplTest : SysuiTestCase() {
private lateinit var notificationInflater: NotificationRowContentBinderImpl
private lateinit var builder: Notification.Builder
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index c1f2cb77..e4945fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,6 +1,5 @@
package com.android.systemui.statusbar.notification.stack
-import android.platform.test.annotations.DisableFlags
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
@@ -21,7 +20,6 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -72,32 +70,6 @@
}
@Test
- @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
- fun testShadeWidth_BasedOnFractionToShade() {
- setFractionToShade(0f)
- setOnLockscreen(true)
-
- shelf.updateActualWidth(/* fractionToShade */ 0f, /* shortestWidth */ 10f)
- assertTrue(shelf.actualWidth == 10)
-
- shelf.updateActualWidth(/* fractionToShade */ 0.5f, /* shortestWidth */ 10f)
- assertTrue(shelf.actualWidth == 20)
-
- shelf.updateActualWidth(/* fractionToShade */ 1f, /* shortestWidth */ 10f)
- assertTrue(shelf.actualWidth == 30)
- }
-
- @Test
- @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
- fun testShelfIsLong_WhenNotOnLockscreen() {
- setFractionToShade(0f)
- setOnLockscreen(false)
-
- shelf.updateActualWidth(/* fraction */ 0f, /* shortestWidth */ 10f)
- assertTrue(shelf.actualWidth == 30)
- }
-
- @Test
fun testX_inViewForClick() {
val isXInView =
shelf.isXInView(/* localX */ 5f, /* slop */ 5f, /* left */ 0f, /* right */ 10f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index a6b2729..e9c16c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -295,7 +295,6 @@
@Mock private Bubbles mBubbles;
@Mock private NoteTaskController mNoteTaskController;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
- @Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@Mock private Lazy<NotificationShadeWindowViewController>
mNotificationShadeWindowViewControllerLazy;
@@ -580,7 +579,6 @@
mDemoModeController,
mNotificationShadeDepthControllerLazy,
mStatusBarTouchableRegionManager,
- mNotificationIconAreaController,
mBrightnessSliderFactory,
mScreenOffAnimationController,
mWallpaperController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 0d06b64..dd03ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -110,7 +110,6 @@
mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
when(mShadeViewController.getShadeHeadsUpTracker()).thenReturn(mShadeHeadsUpTracker);
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
- mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
mPhoneStatusBarTransitions,
@@ -197,7 +196,6 @@
when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight);
HeadsUpAppearanceController newController = new HeadsUpAppearanceController(
- mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
mPhoneStatusBarTransitions,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 7f33c23..eb1e28b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -26,13 +26,10 @@
import static org.mockito.Mockito.when;
import android.content.res.Resources;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.util.BurnInHelperKt;
import com.android.systemui.log.LogBuffer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
deleted file mode 100644
index 8dfbb37..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.phone;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.platform.test.annotations.DisableFlags;
-import android.testing.TestableLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
-import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.wm.shell.bubbles.Bubbles;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-@DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
-public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
-
- @Mock
- private NotificationListener mListener;
- @Mock
- StatusBarStateController mStatusBarStateController;
- @Mock
- NotificationWakeUpCoordinator mWakeUpCoordinator;
- @Mock
- KeyguardBypassController mKeyguardBypassController;
- @Mock
- NotificationMediaManager mNotificationMediaManager;
- @Mock
- DozeParameters mDozeParameters;
- @Mock
- SectionStyleProvider mSectionStyleProvider;
- @Mock
- DarkIconDispatcher mDarkIconDispatcher;
- @Mock
- StatusBarWindowController mStatusBarWindowController;
- @Mock
- ScreenOffAnimationController mScreenOffAnimationController;
- private LegacyNotificationIconAreaControllerImpl mController;
- @Mock
- private Bubbles mBubbles;
- @Mock private DemoModeController mDemoModeController;
- @Mock
- private NotificationIconContainer mAodIcons;
- @Mock
- private FeatureFlags mFeatureFlags;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mController = new LegacyNotificationIconAreaControllerImpl(
- mContext,
- mStatusBarStateController,
- mWakeUpCoordinator,
- mKeyguardBypassController,
- mNotificationMediaManager,
- mListener,
- mDozeParameters,
- mSectionStyleProvider,
- Optional.of(mBubbles),
- mDemoModeController,
- mDarkIconDispatcher,
- mFeatureFlags,
- mStatusBarWindowController,
- mScreenOffAnimationController);
- }
-
- @Test
- public void testNotificationIcons_settingHideIcons() {
- mController.mSettingsListener.onStatusBarIconsBehaviorChanged(true);
-
- assertFalse(mController.shouldShouldLowPriorityIcons());
- }
-
- @Test
- public void testNotificationIcons_settingShowIcons() {
- mController.mSettingsListener.onStatusBarIconsBehaviorChanged(false);
-
- assertTrue(mController.shouldShouldLowPriorityIcons());
- }
-
- @Test
- @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- public void testAppearResetsTranslation() {
- mController.setupAodIcons(mAodIcons);
- when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
- mController.appearAodIcons();
- verify(mAodIcons).setTranslationY(0);
- verify(mAodIcons).setAlpha(1.0f);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 9d97e5a..15958ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,7 +23,6 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -73,18 +72,6 @@
}
@Test
- fun calculateWidthFor_fiveIcons_widthForFourIcons() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
- iconContainer.setActualPaddingStart(10f)
- iconContainer.setActualPaddingEnd(10f)
- iconContainer.setIconSize(10)
- assertEquals(
- /* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
- /* actual= */ 60f
- )
- }
-
- @Test
fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
@@ -213,19 +200,6 @@
}
@Test
- fun shouldForceOverflow_appearingAboveSpeedBump_true() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
- val forceOverflow =
- iconContainer.shouldForceOverflow(
- /* i= */ 1,
- /* speedBumpIndex= */ 0,
- /* iconAppearAmount= */ 1f,
- /* maxVisibleIcons= */ 5
- )
- assertTrue(forceOverflow)
- }
-
- @Test
fun shouldForceOverflow_moreThanMaxVisible_true() {
val forceOverflow =
iconContainer.shouldForceOverflow(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index af5e60e..9b61105 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1068,7 +1068,7 @@
public void testShowBouncerOrKeyguard_needsFullScreen() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
verify(mCentralSurfaces).hideKeyguard();
verify(mPrimaryBouncerInteractor).show(true);
}
@@ -1084,7 +1084,7 @@
.thenReturn(KeyguardState.LOCKSCREEN);
reset(mCentralSurfaces);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
verify(mPrimaryBouncerInteractor).show(true);
verify(mCentralSurfaces).showKeyguard();
}
@@ -1092,11 +1092,26 @@
@Test
@DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
+ boolean isFalsingReset = false;
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
verify(mCentralSurfaces, never()).hideKeyguard();
+ verify(mPrimaryBouncerInteractor).show(true);
+ }
+
+ @Test
+ @DisableSceneContainer
+ public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() {
+ boolean isFalsingReset = true;
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+ verify(mCentralSurfaces, never()).hideKeyguard();
+
+ // Do not refresh the full screen bouncer if the call is from falsing
verify(mPrimaryBouncerInteractor, never()).show(true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 58ad835..bea027f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -39,7 +39,6 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -64,7 +63,6 @@
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
@@ -95,7 +93,6 @@
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
- private NotificationIconAreaController mMockNotificationAreaController;
private ShadeExpansionStateManager mShadeExpansionStateManager;
private OngoingCallController mOngoingCallController;
private SystemStatusAnimationScheduler mAnimationScheduler;
@@ -931,13 +928,11 @@
mCollapsedStatusBarViewModel = new FakeCollapsedStatusBarViewModel();
mCollapsedStatusBarViewBinder = new FakeCollapsedStatusBarViewBinder();
- setUpNotificationIconAreaController();
return new CollapsedStatusBarFragment(
mStatusBarFragmentComponentFactory,
mOngoingCallController,
mAnimationScheduler,
mLocationPublisher,
- mMockNotificationAreaController,
mShadeExpansionStateManager,
mStatusBarIconController,
mIconManagerFactory,
@@ -970,14 +965,6 @@
.thenReturn(mHeadsUpAppearanceController);
}
- private void setUpNotificationIconAreaController() {
- mMockNotificationAreaController = mock(NotificationIconAreaController.class);
- View notificationAreaInner =
- LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null);
- when(mMockNotificationAreaController.getNotificationInnerAreaView())
- .thenReturn(notificationAreaInner);
- }
-
/**
* Configure mocks to return values consistent with the secure camera animating itself launched
* over the keyguard.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index b0acd03..2e6d0fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -385,7 +385,7 @@
private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy {
private val mContentResolver = mock(ContentResolver::class.java)
- private val settingToValueMap: MutableMap<String, String> = mutableMapOf()
+ private val settingToValueMap: MutableMap<String, String?> = mutableMapOf()
override fun getContentResolver() = mContentResolver
@@ -399,15 +399,15 @@
return settingToValueMap[name] ?: ""
}
- override fun putString(name: String, value: String): Boolean {
+ override fun putString(name: String, value: String?): Boolean {
settingToValueMap[name] = value
return true
}
override fun putString(
name: String,
- value: String,
- tag: String,
+ value: String?,
+ tag: String?,
makeDefault: Boolean
): Boolean {
settingToValueMap[name] = value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index eaeece9..00b8cd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -561,7 +561,7 @@
) : UserSettingsProxy {
private val mContentResolver = mock(ContentResolver::class.java)
- private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> =
+ private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String?>> =
mutableMapOf()
override fun getContentResolver() = mContentResolver
@@ -577,7 +577,7 @@
override fun putString(
name: String,
- value: String,
+ value: String?,
overrideableByRestore: Boolean
): Boolean {
userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value)
@@ -586,22 +586,22 @@
override fun putString(
name: String,
- value: String,
- tag: String,
+ value: String?,
+ tag: String?,
makeDefault: Boolean
): Boolean {
putStringForUser(name, value, DEFAULT_USER_ID)
return true
}
- override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean {
+ override fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean {
userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value))
return true
}
override fun putStringForUser(
name: String,
- value: String,
+ value: String?,
tag: String?,
makeDefault: Boolean,
userHandle: Int,
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index 185deea..a61233a 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -16,10 +16,12 @@
package android.content
+import com.android.systemui.SysuiTestableContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.util.mockito.mock
-var Kosmos.applicationContext: Context by
+var Kosmos.testableContext: SysuiTestableContext by
Kosmos.Fixture { testCase.context.apply { ensureTestableResources() } }
+var Kosmos.applicationContext: Context by Kosmos.Fixture { testableContext }
val Kosmos.mockedContext: Context by Kosmos.Fixture { mock<Context>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
index d5411ad..3680e65 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -19,6 +19,7 @@
import android.content.mockedContext
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -42,5 +43,6 @@
activityStarter = activityStarter,
powerInteractor = powerInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ communalSceneInteractor = communalSceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index edf4bcc..1d2bce2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -17,10 +17,9 @@
package com.android.systemui.education.data.repository
import com.android.systemui.kosmos.Kosmos
-import java.time.Clock
import java.time.Instant
var Kosmos.contextualEducationRepository: ContextualEducationRepository by
- Kosmos.Fixture { FakeContextualEducationRepository(fakeEduClock) }
+ Kosmos.Fixture { FakeContextualEducationRepository() }
-var Kosmos.fakeEduClock: Clock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
+var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index 3816e1b..aa1968a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -18,12 +18,11 @@
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.education.data.model.GestureEduModel
-import java.time.Clock
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+class FakeContextualEducationRepository : ContextualEducationRepository {
private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
private val _gestureEduModels = MutableStateFlow(GestureEduModel())
@@ -44,16 +43,11 @@
return gestureEduModelsFlow
}
- override suspend fun incrementSignalCount(gestureType: GestureType) {
- val originalModel = _gestureEduModels.value
- _gestureEduModels.value =
- originalModel.copy(
- signalCount = _gestureEduModels.value.signalCount + 1,
- )
- }
-
- override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
- val originalModel = _gestureEduModels.value
- _gestureEduModels.value = originalModel.copy(lastShortcutTriggeredTime = clock.instant())
+ override suspend fun updateGestureEduModel(
+ gestureType: GestureType,
+ transform: (GestureEduModel) -> GestureEduModel
+ ) {
+ val currentModel = _gestureEduModels.value
+ _gestureEduModels.value = transform(currentModel)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
index 513c143..c9a5d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -19,8 +19,9 @@
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
+import kotlin.time.Duration
-class FakeEduClock(private val base: Instant) : Clock() {
+class FakeEduClock(private var base: Instant) : Clock() {
private val zone: ZoneId = ZoneId.of("UTC")
override fun instant(): Instant {
@@ -34,4 +35,8 @@
override fun getZone(): ZoneId {
return zone
}
+
+ fun offset(duration: Duration) {
+ base = base.plusSeconds(duration.inWholeSeconds)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
index a7b322b..5c99a7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.education.data.repository.contextualEducationRepository
+import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -28,6 +29,7 @@
backgroundScope = testScope.backgroundScope,
backgroundDispatcher = testDispatcher,
repository = contextualEducationRepository,
- selectedUserInteractor = selectedUserInteractor
+ selectedUserInteractor = selectedUserInteractor,
+ clock = fakeEduClock
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
index fb4e901..5088677 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.domain.interactor
+import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -23,7 +24,8 @@
Kosmos.Fixture {
KeyboardTouchpadEduInteractor(
backgroundScope = testScope.backgroundScope,
- contextualEducationInteractor = contextualEducationInteractor
+ contextualEducationInteractor = contextualEducationInteractor,
+ clock = fakeEduClock
)
}
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 727de9e..4571c19 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
@@ -74,6 +74,8 @@
private val _dozeTimeTick = MutableStateFlow<Long>(0L)
override val dozeTimeTick = _dozeTimeTick
+ override val showDismissibleKeyguard = MutableStateFlow<Long>(0L)
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -206,6 +208,10 @@
_dozeTimeTick.value = millis
}
+ override fun showDismissibleKeyguard() {
+ showDismissibleKeyguard.value = showDismissibleKeyguard.value + 1
+ }
+
override fun setLastDozeTapToWakePosition(position: Point) {
_lastDozeTapToWakePosition.value = position
}
@@ -216,6 +222,9 @@
override fun setDreaming(isDreaming: Boolean) {
_isDreaming.value = isDreaming
+ // Intentionally set both for testing, to avoid races with merge() in the interactor that
+ // would make testing difficult
+ _isDreamingWithOverlay.value = isDreaming
}
fun setDreamingWithOverlay(isDreaming: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index c5da10e..b68d6a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -33,6 +33,7 @@
fromAodTransitionInteractor = { fromAodTransitionInteractor },
fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
+ fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
sceneInteractor = sceneInteractor
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 82860fc..b9443bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -39,6 +39,7 @@
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
+ alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
alternateBouncerToLockscreenTransitionViewModel =
alternateBouncerToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 550ecb3..19b32bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -20,7 +20,6 @@
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
@@ -33,7 +32,6 @@
authController = authController,
touchHandling = keyguardTouchHandlingViewModel,
shadeInteractor = shadeInteractor,
- applicationScope = applicationCoroutineScope,
unfoldTransitionInteractor = unfoldTransitionInteractor,
occlusionInteractor = sceneContainerOcclusionInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
similarity index 78%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
index 299b22e..5d70ed6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
@@ -18,13 +18,11 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
-val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
+val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
Kosmos.Fixture {
- QuickSettingsShadeSceneViewModel(
+ QuickSettingsShadeSceneActionsViewModel(
shadeInteractor = shadeInteractor,
- overlayShadeViewModel = overlayShadeViewModel,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
index 299b22e..5ad5cb2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
@@ -14,17 +14,18 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
+import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModelFactory
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by
+val Kosmos.quickSettingsShadeSceneContentViewModel: QuickSettingsShadeSceneContentViewModel by
Kosmos.Fixture {
- QuickSettingsShadeSceneViewModel(
- shadeInteractor = shadeInteractor,
- overlayShadeViewModel = overlayShadeViewModel,
+ QuickSettingsShadeSceneContentViewModel(
+ overlayShadeViewModelFactory = overlayShadeViewModelFactory,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
index 8fb370c..32a5614 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
@@ -18,15 +18,23 @@
import android.content.res.mainResources
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.settings.brightnessSliderControllerFactory
-val Kosmos.brightnessMirrorViewModel by
- Kosmos.Fixture {
- BrightnessMirrorViewModel(
- brightnessMirrorShowingInteractor,
- mainResources,
- brightnessSliderControllerFactory,
- )
+val Kosmos.brightnessMirrorViewModel by Fixture {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+}
+
+val Kosmos.brightnessMirrorViewModelFactory by Fixture {
+ object : BrightnessMirrorViewModel.Factory {
+ override fun create(): BrightnessMirrorViewModel {
+ return brightnessMirrorViewModel
+ }
}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index ea02d0c..6d488d2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -18,10 +18,13 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
@@ -86,6 +89,11 @@
delegate.assertFlagValid()
delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer)
}
+
+ fun setSplitShade(splitShade: Boolean) {
+ delegate.assertFlagValid()
+ delegate.setSplitShade(splitShade)
+ }
}
/** Sets up shade state for tests for a specific value of the scene container flag. */
@@ -117,11 +125,16 @@
fun setQsFullscreen(qsFullscreen: Boolean)
fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
+
+ fun setSplitShade(splitShade: Boolean)
}
/** Sets up shade state for tests when the scene container flag is disabled. */
-class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: FakeShadeRepository) :
- ShadeTestUtilDelegate {
+class ShadeTestUtilLegacyImpl(
+ val testScope: TestScope,
+ val shadeRepository: FakeShadeRepository,
+ val context: SysuiTestableContext
+) : ShadeTestUtilDelegate {
override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
shadeRepository.setLegacyShadeExpansion(shadeExpansion)
shadeRepository.setQsExpansion(qsExpansion)
@@ -168,11 +181,22 @@
override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) {
shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded)
}
+
+ override fun setSplitShade(splitShade: Boolean) {
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, splitShade)
+ testScope.runCurrent()
+ }
}
/** Sets up shade state for tests when the scene container flag is enabled. */
-class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: SceneInteractor) :
- ShadeTestUtilDelegate {
+class ShadeTestUtilSceneImpl(
+ val testScope: TestScope,
+ val sceneInteractor: SceneInteractor,
+ val shadeRepository: ShadeRepository,
+ val context: SysuiTestableContext,
+) : ShadeTestUtilDelegate {
val isUserInputOngoing = MutableStateFlow(true)
override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
@@ -263,6 +287,14 @@
testScope.runCurrent()
}
+ override fun setSplitShade(splitShade: Boolean) {
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, splitShade)
+ shadeRepository.setShadeLayoutWide(splitShade)
+ testScope.runCurrent()
+ }
+
override fun assertFlagValid() {
Assert.assertTrue(SceneContainerFlag.isEnabled)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt
index 9eeb345..a1551e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtilKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
+import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -26,9 +27,14 @@
Kosmos.Fixture {
ShadeTestUtil(
if (SceneContainerFlag.isEnabled) {
- ShadeTestUtilSceneImpl(testScope, sceneInteractor)
+ ShadeTestUtilSceneImpl(
+ testScope,
+ sceneInteractor,
+ fakeShadeRepository,
+ testableContext
+ )
} else {
- ShadeTestUtilLegacyImpl(testScope, fakeShadeRepository)
+ ShadeTestUtilLegacyImpl(testScope, fakeShadeRepository, testableContext)
}
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index bfd6614..54208b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -44,7 +44,7 @@
ShadeInteractorSceneContainerImpl(
scope = applicationCoroutineScope,
sceneInteractor = sceneInteractor,
- sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+ shadeRepository = shadeRepository,
)
}
val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
similarity index 74%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
index 72a80d4..9bf4756 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
@@ -17,8 +17,13 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
import com.android.systemui.shade.domain.interactor.shadeInteractor
-val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
- Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
+val Kosmos.notificationsShadeSceneActionsViewModel:
+ NotificationsShadeSceneActionsViewModel by Fixture {
+ NotificationsShadeSceneActionsViewModel(
+ shadeInteractor = shadeInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt
new file mode 100644
index 0000000..9240102
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneContentViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneContentViewModel
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.notificationsShadeSceneContentViewModel:
+ NotificationsShadeSceneContentViewModel by Fixture {
+ NotificationsShadeSceneContentViewModel(
+ deviceEntryInteractor = deviceEntryInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
index cd4fab8..6252d44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeWindowModelKosmos.kt
@@ -16,8 +16,14 @@
package com.android.systemui.shade.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by
- Kosmos.Fixture { NotificationShadeWindowModel(keyguardTransitionInteractor) }
+ Kosmos.Fixture {
+ NotificationShadeWindowModel(
+ keyguardTransitionInteractor,
+ keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
index 8d4d547..00f1526 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
@@ -17,15 +17,22 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by
Kosmos.Fixture {
OverlayShadeViewModel(
- applicationScope = applicationCoroutineScope,
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
)
}
+
+val Kosmos.overlayShadeViewModelFactory: OverlayShadeViewModel.Factory by
+ Kosmos.Fixture {
+ object : OverlayShadeViewModel.Factory {
+ override fun create(): OverlayShadeViewModel {
+ return overlayShadeViewModel
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 0e21698..7eb9f34 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -19,7 +19,6 @@
import android.content.applicationContext
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
@@ -31,7 +30,6 @@
val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
Kosmos.Fixture {
ShadeHeaderViewModel(
- applicationScope = applicationCoroutineScope,
context = applicationContext,
activityStarter = activityStarter,
sceneInteractor = sceneInteractor,
@@ -43,3 +41,12 @@
broadcastDispatcher = broadcastDispatcher,
)
}
+
+val Kosmos.shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory by
+ Kosmos.Fixture {
+ object : ShadeHeaderViewModel.Factory {
+ override fun create(): ShadeHeaderViewModel {
+ return shadeHeaderViewModel
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
similarity index 66%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
index 72a80d4..2387aa8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,13 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.shade.domain.interactor.shadeInteractor
-val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by
- Kosmos.Fixture { NotificationsShadeSceneViewModel(shadeInteractor) }
+val Kosmos.shadeSceneActionsViewModel: ShadeSceneActionsViewModel by Fixture {
+ ShadeSceneActionsViewModel(
+ qsSceneAdapter = qsSceneAdapter,
+ shadeInteractor = shadeInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
new file mode 100644
index 0000000..7097d31
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
+
+val Kosmos.shadeSceneContentViewModel: ShadeSceneContentViewModel by Fixture {
+ ShadeSceneContentViewModel(
+ shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
+ qsSceneAdapter = qsSceneAdapter,
+ brightnessMirrorViewModelFactory = brightnessMirrorViewModelFactory,
+ mediaCarouselInteractor = mediaCarouselInteractor,
+ shadeInteractor = shadeInteractor,
+ footerActionsViewModelFactory = footerActionsViewModelFactory,
+ footerActionsController = footerActionsController,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
deleted file mode 100644
index 2c5a0f4..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
+++ /dev/null
@@ -1,42 +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.shade.ui.viewmodel
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.qs.footerActionsController
-import com.android.systemui.qs.footerActionsViewModelFactory
-import com.android.systemui.qs.ui.adapter.qsSceneAdapter
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
-
-val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
- Kosmos.Fixture {
- ShadeSceneViewModel(
- shadeHeaderViewModel = shadeHeaderViewModel,
- qsSceneAdapter = qsSceneAdapter,
- brightnessMirrorViewModel = brightnessMirrorViewModel,
- mediaCarouselInteractor = mediaCarouselInteractor,
- shadeInteractor = shadeInteractor,
- footerActionsViewModelFactory = footerActionsViewModelFactory,
- footerActionsController = footerActionsController,
- sceneInteractor = sceneInteractor,
- unfoldTransitionInteractor = unfoldTransitionInteractor,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 8909d75..3234e66 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.policy.splitShadeStateController
@@ -29,7 +30,8 @@
SharedNotificationContainerInteractor(
configurationRepository = configurationRepository,
context = applicationContext,
- splitShadeStateController = splitShadeStateController,
+ splitShadeStateController = { splitShadeStateController },
+ shadeInteractor = { shadeInteractor },
keyguardInteractor = keyguardInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
largeScreenHeaderHelperLazy = { largeScreenHeaderHelper }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index ee3216b..bc1363a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -29,22 +29,20 @@
import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
import com.android.systemui.statusbar.notification.ui.viewbinder.headsUpNotificationViewBinder
-import com.android.systemui.statusbar.phone.notificationIconAreaController
import java.util.Optional
val Kosmos.notificationListViewBinder by Fixture {
NotificationListViewBinder(
- viewModel = notificationListViewModel,
backgroundDispatcher = testDispatcher,
+ hiderTracker = displaySwitchNotificationsHiderTracker,
configuration = configurationState,
falsingManager = falsingManager,
hunBinder = headsUpNotificationViewBinder,
- iconAreaController = notificationIconAreaController,
loggerOptional = Optional.of(notificationStatsLogger),
metricsLogger = metricsLogger,
- hiderTracker = displaySwitchNotificationsHiderTracker,
nicBinder = notificationIconContainerShelfViewBinder,
notificationActivityStarter = { notificationActivityStarter },
silentHeaderController = silentHeaderController,
+ viewModel = notificationListViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index afb8acb..20dc668 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -21,7 +21,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -30,7 +29,6 @@
dumpManager = dumpManager,
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
- shadeSceneViewModel = shadeSceneViewModel,
headsUpNotificationInteractor = headsUpNotificationInteractor,
featureFlags = featureFlagsClassic,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
index 0b5a68e..4a6757d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -53,7 +53,6 @@
notificationShadeWindowController,
notificationWakeUpCoordinator,
authController,
- notificationIconAreaController,
shadeLockscreenInteractor,
dozeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt
deleted file mode 100644
index d44e061..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerKosmos.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
-
-var Kosmos.notificationIconAreaController by Fixture { mock<NotificationIconAreaController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
deleted file mode 100644
index d117466..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.settings;
-
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class FakeSettings implements SecureSettings, SystemSettings {
- private final Map<SettingsKey, String> mValues = new HashMap<>();
- private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
- new HashMap<>();
- private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
- private final CoroutineDispatcher mDispatcher;
-
- public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
- @UserIdInt
- private int mUserId = UserHandle.USER_CURRENT;
-
- private final CurrentUserIdProvider mCurrentUserProvider;
-
- /**
- * @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
- * by main test scope.
- */
- @Deprecated
- public FakeSettings() {
- mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
- mCurrentUserProvider = () -> mUserId;
- }
-
- public FakeSettings(CoroutineDispatcher dispatcher) {
- mDispatcher = dispatcher;
- mCurrentUserProvider = () -> mUserId;
- }
-
- public FakeSettings(CoroutineDispatcher dispatcher, CurrentUserIdProvider currentUserProvider) {
- mDispatcher = dispatcher;
- mCurrentUserProvider = currentUserProvider;
- }
-
- @VisibleForTesting
- FakeSettings(String initialKey, String initialValue) {
- this();
- putString(initialKey, initialValue);
- }
-
- @VisibleForTesting
- FakeSettings(Map<String, String> initialValues) {
- this();
- for (Map.Entry<String, String> kv : initialValues.entrySet()) {
- putString(kv.getKey(), kv.getValue());
- }
- }
-
- @Override
- @NonNull
- public ContentResolver getContentResolver() {
- throw new UnsupportedOperationException(
- "FakeSettings.getContentResolver is not implemented");
- }
-
- @NonNull
- @Override
- public CurrentUserIdProvider getCurrentUserProvider() {
- return mCurrentUserProvider;
- }
-
- @NonNull
- @Override
- public CoroutineDispatcher getBackgroundDispatcher() {
- return mDispatcher;
- }
-
- @Override
- public void registerContentObserverForUserSync(@NonNull Uri uri, boolean notifyDescendants,
- @NonNull ContentObserver settingsObserver, int userHandle) {
- List<ContentObserver> observers;
- if (userHandle == UserHandle.USER_ALL) {
- mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
- observers = mContentObserversAllUsers.get(uri.toString());
- } else {
- SettingsKey key = new SettingsKey(userHandle, uri.toString());
- mContentObservers.putIfAbsent(key, new ArrayList<>());
- observers = mContentObservers.get(key);
- }
- observers.add(settingsObserver);
- }
-
- @Override
- public void unregisterContentObserverSync(@NonNull ContentObserver settingsObserver) {
- for (List<ContentObserver> observers : mContentObservers.values()) {
- observers.remove(settingsObserver);
- }
- for (List<ContentObserver> observers : mContentObserversAllUsers.values()) {
- observers.remove(settingsObserver);
- }
- }
-
- @NonNull
- @Override
- public Uri getUriFor(@NonNull String name) {
- return Uri.withAppendedPath(CONTENT_URI, name);
- }
-
- public void setUserId(@UserIdInt int userId) {
- mUserId = userId;
- }
-
- @Override
- public int getUserId() {
- return mUserId;
- }
-
- @Override
- public String getString(@NonNull String name) {
- return getStringForUser(name, getUserId());
- }
-
- @Override
- public String getStringForUser(@NonNull String name, int userHandle) {
- return mValues.get(new SettingsKey(userHandle, getUriFor(name).toString()));
- }
-
- @Override
- public boolean putString(@NonNull String name, @NonNull String value,
- boolean overrideableByRestore) {
- return putStringForUser(name, value, null, false, getUserId(), overrideableByRestore);
- }
-
- @Override
- public boolean putString(@NonNull String name, @NonNull String value) {
- return putString(name, value, false);
- }
-
- @Override
- public boolean putStringForUser(@NonNull String name, @NonNull String value, int userHandle) {
- return putStringForUser(name, value, null, false, userHandle, false);
- }
-
- @Override
- public boolean putStringForUser(@NonNull String name, @NonNull String value, String tag,
- boolean makeDefault, int userHandle, boolean overrideableByRestore) {
- SettingsKey key = new SettingsKey(userHandle, getUriFor(name).toString());
- mValues.put(key, value);
-
- Uri uri = getUriFor(name);
- for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), 0, userHandle);
- }
- for (ContentObserver observer :
- mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), 0, userHandle);
- }
- return true;
- }
-
- @Override
- public boolean putString(@NonNull String name, @NonNull String value, @NonNull String tag,
- boolean makeDefault) {
- return putString(name, value);
- }
-
- private static class SettingsKey extends Pair<Integer, String> {
- SettingsKey(Integer first, String second) {
- super(first, second);
- }
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
new file mode 100644
index 0000000..e5d113b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.settings
+
+import android.annotation.UserIdInt
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.UserHandle
+import android.util.Pair
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.util.settings.SettingsProxy.CurrentUserIdProvider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+
+class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy {
+ private val values = mutableMapOf<SettingsKey, String?>()
+ private val contentObservers = mutableMapOf<SettingsKey, MutableList<ContentObserver>>()
+ private val contentObserversAllUsers = mutableMapOf<String, MutableList<ContentObserver>>()
+
+ override val backgroundDispatcher: CoroutineDispatcher
+
+ @UserIdInt override var userId = UserHandle.USER_CURRENT
+ override val currentUserProvider: CurrentUserIdProvider
+
+ @Deprecated(
+ """Please use FakeSettings(testDispatcher) to provide the same dispatcher used
+ by main test scope."""
+ )
+ constructor() {
+ backgroundDispatcher = StandardTestDispatcher(scheduler = null, name = null)
+ currentUserProvider = CurrentUserIdProvider { userId }
+ }
+
+ constructor(dispatcher: CoroutineDispatcher) {
+ backgroundDispatcher = dispatcher
+ currentUserProvider = CurrentUserIdProvider { userId }
+ }
+
+ constructor(dispatcher: CoroutineDispatcher, currentUserProvider: CurrentUserIdProvider) {
+ backgroundDispatcher = dispatcher
+ this.currentUserProvider = currentUserProvider
+ }
+
+ @VisibleForTesting
+ internal constructor(initialKey: String, initialValue: String) : this() {
+ putString(initialKey, initialValue)
+ }
+
+ @VisibleForTesting
+ internal constructor(initialValues: Map<String, String>) : this() {
+ for ((key, value) in initialValues) {
+ putString(key, value)
+ }
+ }
+
+ override fun getContentResolver(): ContentResolver {
+ throw UnsupportedOperationException("FakeSettings.getContentResolver is not implemented")
+ }
+
+ override fun registerContentObserverForUserSync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) {
+ if (userHandle == UserHandle.USER_ALL) {
+ contentObserversAllUsers
+ .getOrPut(uri.toString()) { mutableListOf() }
+ .add(settingsObserver)
+ } else {
+ val key = SettingsKey(userHandle, uri.toString())
+ contentObservers.getOrPut(key) { mutableListOf() }.add(settingsObserver)
+ }
+ }
+
+ override fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
+ contentObservers.values.onEach { it.remove(settingsObserver) }
+ contentObserversAllUsers.values.onEach { it.remove(settingsObserver) }
+ }
+
+ override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+ suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserver(uri, settingsObserver)
+ }
+
+ override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job =
+ advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverAsync(uri, settingsObserver)
+ }
+
+ override suspend fun registerContentObserver(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserver(
+ uri,
+ notifyForDescendants,
+ settingsObserver
+ )
+ }
+
+ override fun registerContentObserverAsync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverAsync(
+ uri,
+ notifyForDescendants,
+ settingsObserver
+ )
+ }
+
+ override suspend fun registerContentObserverForUser(
+ name: String,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUser(name, settingsObserver, userHandle)
+ }
+
+ override fun registerContentObserverForUserAsync(
+ name: String,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ name,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun unregisterContentObserverAsync(settingsObserver: ContentObserver): Job =
+ advanceDispatcher {
+ super<UserSettingsProxy>.unregisterContentObserverAsync(settingsObserver)
+ }
+
+ override suspend fun registerContentObserverForUser(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUser(uri, settingsObserver, userHandle)
+ }
+
+ override fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ uri,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int,
+ registered: Runnable
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ uri,
+ settingsObserver,
+ userHandle,
+ registered
+ )
+ }
+
+ override suspend fun registerContentObserverForUser(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUser(
+ name,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun registerContentObserverForUserAsync(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ name,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun registerContentObserverForUserAsync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ uri,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun getUriFor(name: String): Uri {
+ return Uri.withAppendedPath(CONTENT_URI, name)
+ }
+
+ override fun getString(name: String): String? {
+ return getStringForUser(name, userId)
+ }
+
+ override fun getStringForUser(name: String, userHandle: Int): String? {
+ return values[SettingsKey(userHandle, getUriFor(name).toString())]
+ }
+
+ override fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean {
+ return putStringForUser(name, value, null, false, userId, overrideableByRestore)
+ }
+
+ override fun putString(name: String, value: String?): Boolean {
+ return putString(name, value, false)
+ }
+
+ override fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean {
+ return putStringForUser(name, value, null, false, userHandle, false)
+ }
+
+ override fun putStringForUser(
+ name: String,
+ value: String?,
+ tag: String?,
+ makeDefault: Boolean,
+ userHandle: Int,
+ overrideableByRestore: Boolean
+ ): Boolean {
+ val key = SettingsKey(userHandle, getUriFor(name).toString())
+ values[key] = value
+ val uri = getUriFor(name)
+ contentObservers[key]?.onEach { it.dispatchChange(false, listOf(uri), 0, userHandle) }
+ contentObserversAllUsers[uri.toString()]?.onEach {
+ it.dispatchChange(false, listOf(uri), 0, userHandle)
+ }
+ return true
+ }
+
+ override fun putString(
+ name: String,
+ value: String?,
+ tag: String?,
+ makeDefault: Boolean
+ ): Boolean {
+ return putString(name, value)
+ }
+
+ /** Runs current jobs on dispatcher after calling the method. */
+ private fun <T> advanceDispatcher(f: () -> T): T {
+ val result = f()
+ testDispatcherRunCurrent()
+ return result
+ }
+
+ private suspend fun <T> suspendAdvanceDispatcher(f: suspend () -> T): T {
+ val result = f()
+ testDispatcherRunCurrent()
+ return result
+ }
+
+ private fun testDispatcherRunCurrent() {
+ val testDispatcher = backgroundDispatcher as? TestDispatcher
+ testDispatcher?.scheduler?.runCurrent()
+ }
+
+ private data class SettingsKey(val first: Int, val second: String) :
+ Pair<Int, String>(first, second)
+
+ companion object {
+ val CONTENT_URI = Uri.parse("content://settings/fake")
+ }
+}
diff --git a/packages/overlays/HsumConfigOverlay/Android.bp b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
similarity index 92%
rename from packages/overlays/HsumConfigOverlay/Android.bp
rename to packages/overlays/HsumDefaultConfigOverlay/Android.bp
index 050b1f0..bff2f9b 100644
--- a/packages/overlays/HsumConfigOverlay/Android.bp
+++ b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
@@ -8,7 +8,7 @@
}
runtime_resource_overlay {
- name: "HsumConfigOverlay",
+ name: "HsumDefaultConfigOverlay",
certificate: "platform",
product_specific: true,
diff --git a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
similarity index 93%
rename from packages/overlays/HsumConfigOverlay/AndroidManifest.xml
rename to packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
index cd7a879..dcd1741 100644
--- a/packages/overlays/HsumConfigOverlay/AndroidManifest.xml
+++ b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.overlay.hsumconfig"
+ package="com.android.internal.overlay.hsum.defaultconfig"
android:versionCode="1"
android:versionName="1.0">
<overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
diff --git a/packages/overlays/HsumConfigOverlay/OWNERS b/packages/overlays/HsumDefaultConfigOverlay/OWNERS
similarity index 100%
rename from packages/overlays/HsumConfigOverlay/OWNERS
rename to packages/overlays/HsumDefaultConfigOverlay/OWNERS
diff --git a/packages/overlays/HsumConfigOverlay/res/values/config.xml b/packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
similarity index 100%
rename from packages/overlays/HsumConfigOverlay/res/values/config.xml
rename to packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
index da4dfa9..6c8a486 100644
--- a/proto/src/windowmanager.proto
+++ b/proto/src/windowmanager.proto
@@ -45,6 +45,7 @@
int32 letterbox_inset_top = 18;
int32 letterbox_inset_right = 19;
int32 letterbox_inset_bottom = 20;
+ int32 ui_mode = 21;
}
// Persistent letterboxing configurations
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 2de3c5e..6150343 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -280,10 +280,10 @@
src: "scripts/ravenwood-stats-checker.sh",
test_suites: ["general-tests"],
data: [
- ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_stats.csv}",
- ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_apis.csv}",
- ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_keep_all.txt}",
- ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_dump.txt}",
+ ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_stats.csv}",
+ ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_apis.csv}",
+ ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_keep_all.txt}",
+ ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_dump.txt}",
":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}",
":services.core.ravenwood-base{hoststubgen_services.core_apis.csv}",
":services.core.ravenwood-base{hoststubgen_services.core_keep_all.txt}",
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 68f185e..cc9b70e 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -281,6 +281,12 @@
com.android.server.LocalServices
+com.android.internal.graphics.cam.Cam
+com.android.internal.graphics.cam.CamUtils
+com.android.internal.graphics.cam.Frame
+com.android.internal.graphics.cam.HctSolver
+com.android.internal.graphics.ColorUtils
+
com.android.internal.util.BitUtils
com.android.internal.util.BitwiseInputStream
com.android.internal.util.BitwiseOutputStream
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 56da231..2ce5c2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -78,6 +78,9 @@
private final AccessibilityManagerService mAms;
private final Handler mHandler;
+ /** Thread to wait for virtual mouse creation to complete */
+ private final Thread mCreateVirtualMouseThread;
+
VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
private VirtualMouse mVirtualMouse = null;
@@ -154,34 +157,47 @@
mHandler = new Handler(looper, this);
// Create the virtual mouse on a separate thread since virtual device creation
// should happen on an auxiliary thread, and not from the handler's thread.
- // This is because virtual device creation is a blocking operation and can cause a
- // deadlock if it is called from the handler's thread.
- new Thread(() -> {
+ // This is because the handler thread is the same as the main thread,
+ // and the main thread will be blocked waiting for the virtual device to be created.
+ mCreateVirtualMouseThread = new Thread(() -> {
mVirtualMouse = createVirtualMouse(displayId);
- }).start();
+ });
+ mCreateVirtualMouseThread.start();
+ }
+ /**
+ * Wait for {@code mVirtualMouse} to be created.
+ * This will ensure that {@code mVirtualMouse} is always created before
+ * trying to send mouse events.
+ **/
+ private void waitForVirtualMouseCreation() {
+ try {
+ // Block the current thread until the virtual mouse creation thread completes.
+ mCreateVirtualMouseThread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
}
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void sendVirtualMouseRelativeEvent(float x, float y) {
- if (mVirtualMouse != null) {
- mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder()
- .setRelativeX(x)
- .setRelativeY(y)
- .build()
- );
- }
+ waitForVirtualMouseCreation();
+ mVirtualMouse.sendRelativeEvent(new VirtualMouseRelativeEvent.Builder()
+ .setRelativeX(x)
+ .setRelativeY(y)
+ .build()
+ );
}
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private void sendVirtualMouseButtonEvent(int buttonCode, int actionCode) {
- if (mVirtualMouse != null) {
- mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder()
- .setAction(actionCode)
- .setButtonCode(buttonCode)
- .build()
- );
- }
+ waitForVirtualMouseCreation();
+ mVirtualMouse.sendButtonEvent(new VirtualMouseButtonEvent.Builder()
+ .setAction(actionCode)
+ .setButtonCode(buttonCode)
+ .build()
+ );
}
/**
@@ -205,12 +221,11 @@
case DOWN_MOVE_OR_SCROLL -> -1.0f;
default -> 0.0f;
};
- if (mVirtualMouse != null) {
- mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
- .setYAxisMovement(y)
- .build()
- );
- }
+ waitForVirtualMouseCreation();
+ mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder()
+ .setYAxisMovement(y)
+ .build()
+ );
if (DEBUG) {
Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name()
+ " for scroll action with axis movement (y=" + y + ")");
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0ab6bbc..42f69e9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -223,8 +223,8 @@
// delays (even in case of the Main Thread). It may be fine overall, but would require
// updating the tests (adding a delay there).
mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
- mDevicePresenceProcessor.init(context);
} else if (phase == PHASE_BOOT_COMPLETED) {
+ mDevicePresenceProcessor.init(context);
// Run the Inactive Association Removal job service daily.
InactiveAssociationsRemovalService.schedule(getContext());
mCrossDeviceSyncController.onBootCompleted();
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3123268..0b6d135 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -51,6 +51,7 @@
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
/**
* Maps system settings to system properties.
@@ -345,7 +346,7 @@
// add sys prop sync callback for staged flag values
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_REBOOT_STAGING,
- AsyncTask.THREAD_POOL_EXECUTOR,
+ newSingleThreadScheduledExecutor(),
(DeviceConfig.Properties properties) -> {
for (String flagName : properties.getKeyset()) {
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index 2cea32a..f050090 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -10,6 +10,9 @@
"include-filter": "android.media.audio.cts.AudioFocusTest"
},
{
+ "include-filter": "android.media.audio.cts.AudioPlaybackCaptureTest"
+ },
+ {
"include-filter": "android.media.audio.cts.SpatializerTest"
}
]
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index fe73bfe..feef540 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -385,9 +385,9 @@
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
userId) != 0);
} else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
- updateMandatoryBiometricsForAllProfiles();
+ updateMandatoryBiometricsForAllProfiles(userId);
} else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
- updateMandatoryBiometricsRequirementsForAllProfiles();
+ updateMandatoryBiometricsRequirementsForAllProfiles(userId);
}
}
@@ -431,16 +431,15 @@
public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
- updateMandatoryBiometricsForAllProfiles();
+ updateMandatoryBiometricsForAllProfiles(userId);
}
if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
- updateMandatoryBiometricsRequirementsForAllProfiles();
+ updateMandatoryBiometricsRequirementsForAllProfiles(userId);
}
return mMandatoryBiometricsEnabled.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_STATUS)
&& mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
- && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED)
&& getEnabledForApps(userId)
&& (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
|| mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
@@ -455,25 +454,31 @@
}
}
- private void updateMandatoryBiometricsForAllProfiles() {
- final int mainUserId = mUserManager.getMainUser().getIdentifier();
- for (UserHandle userHandle: mUserManager.getUserProfiles()) {
- mMandatoryBiometricsEnabled.put(userHandle.getIdentifier(),
+ private void updateMandatoryBiometricsForAllProfiles(int userId) {
+ int effectiveUserId = userId;
+ if (mUserManager.getMainUser() != null) {
+ effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ }
+ for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
+ mMandatoryBiometricsEnabled.put(profileUserId,
Settings.Secure.getIntForUser(
mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
- mainUserId) != 0);
+ effectiveUserId) != 0);
}
}
- private void updateMandatoryBiometricsRequirementsForAllProfiles() {
- final int mainUserId = mUserManager.getMainUser().getIdentifier();
- for (UserHandle userHandle: mUserManager.getUserProfiles()) {
- mMandatoryBiometricsRequirementsSatisfied.put(userHandle.getIdentifier(),
+ private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
+ int effectiveUserId = userId;
+ if (mUserManager.getMainUser() != null) {
+ effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ }
+ for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
+ mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
- mainUserId) != 0);
+ effectiveUserId) != 0);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 0fdd57d..dca1491 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -264,4 +264,11 @@
}
});
}
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public int getSensorId() {
+ super.getSensorId_enforcePermission();
+ return mSensorId;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 8dc560b..caa2c1c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -293,4 +293,11 @@
}
});
}
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @Override
+ public int getSensorId() {
+ super.getSensorId_enforcePermission();
+ return mSensorId;
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
index 46b4f48..4490745 100644
--- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
@@ -17,8 +17,10 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiDeviceInfo;
+
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
/**
* Buffer storage to keep incoming messages for later processing. Used to
@@ -83,6 +85,16 @@
return false;
}
+ List<HdmiCecMessage> getBufferedMessagesWithOpcode(int opcode) {
+ List<HdmiCecMessage> messages = new ArrayList<>();
+ for (HdmiCecMessage message : mBuffer) {
+ if (message.getOpcode() == opcode) {
+ messages.add(message);
+ }
+ }
+ return messages;
+ }
+
void processAllMessages() {
// Use the copied buffer.
ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 49888db..154710f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -217,7 +217,9 @@
resetSelectRequestBuffer();
launchDeviceDiscovery();
startQueuedActions();
- if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+ List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer
+ .getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE);
+ if (bufferedActiveSource.isEmpty()) {
if (hasAction(RequestActiveSourceAction.class)) {
Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
removeAction(RequestActiveSourceAction.class);
@@ -236,9 +238,33 @@
}
}
}));
+ } else {
+ addCecDeviceForBufferedActiveSource(bufferedActiveSource.get(0));
}
}
+ // Add a new CEC device with known information from the buffered <Active Source> message. This
+ // helps TvInputCallback#onInputAdded to be called such that the message can be processed and
+ // the TV to switch to the new active input.
+ @ServiceThreadOnly
+ private void addCecDeviceForBufferedActiveSource(HdmiCecMessage bufferedActiveSource) {
+ assertRunOnServiceThread();
+ if (bufferedActiveSource == null) {
+ return;
+ }
+ int source = bufferedActiveSource.getSource();
+ int physicalAddress = HdmiUtils.twoBytesToInt(bufferedActiveSource.getParams());
+ List<Integer> deviceTypes = HdmiUtils.getTypeFromAddress(source);
+ HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(source)
+ .setPhysicalAddress(physicalAddress)
+ .setDisplayName(HdmiUtils.getDefaultDeviceName(source))
+ .setDeviceType(deviceTypes.get(0))
+ .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+ .build();
+ mService.getHdmiCecNetwork().addCecDevice(newDevice);
+ }
+
@ServiceThreadOnly
public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 7746276..3161b77 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -523,8 +523,7 @@
if ((value & 0x80) != 0x00) {
return false;
}
- // Validate than not more than one bit is set
- return (Integer.bitCount(value) <= 1);
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index d32a5ed..819b9a1 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.graphics.PointF;
import android.hardware.display.DisplayViewport;
+import android.hardware.input.KeyboardSystemShortcut;
import android.os.IBinder;
import android.view.InputChannel;
import android.view.inputmethod.InputMethodSubtype;
@@ -227,4 +228,20 @@
* since boot.
*/
public abstract int getLastUsedInputDeviceId();
+
+ /**
+ * Notify Keyboard system shortcut was triggered by the user and handled by the framework.
+ *
+ * NOTE: This is just to notify that a system shortcut was triggered. No further action is
+ * required to execute the said shortcut. This callback is meant for purposes of providing user
+ * hints or logging, etc.
+ *
+ * @param deviceId the device ID of the keyboard using which the shortcut was triggered
+ * @param keycodes the keys pressed for triggering the shortcut
+ * @param modifierState the modifier state of the key event that triggered the shortcut
+ * @param shortcut the shortcut that was triggered
+ *
+ */
+ public abstract void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes,
+ int modifierState, @KeyboardSystemShortcut.SystemShortcut int shortcut);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a06ad14..e555761 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -47,6 +47,7 @@
import android.hardware.input.IInputManager;
import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.IKeyboardBacklightListener;
+import android.hardware.input.IKeyboardSystemShortcutListener;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
@@ -56,6 +57,7 @@
import android.hardware.input.KeyGlyphMap;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.KeyboardLayoutSelectionResult;
+import android.hardware.input.KeyboardSystemShortcut;
import android.hardware.input.TouchCalibration;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
@@ -157,6 +159,7 @@
private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
+ private static final int MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED = 4;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
private static final AdditionalDisplayInputProperties
@@ -306,6 +309,9 @@
// Manages Sticky modifier state
private final StickyModifierStateController mStickyModifierStateController;
+ // Manages keyboard system shortcut callbacks
+ private final KeyboardShortcutCallbackHandler mKeyboardShortcutCallbackHandler;
+
// Manages Keyboard microphone mute led
private final KeyboardLedController mKeyboardLedController;
@@ -461,6 +467,7 @@
injector.getLooper(), injector.getUEventManager())
: new KeyboardBacklightControllerInterface() {};
mStickyModifierStateController = new StickyModifierStateController();
+ mKeyboardShortcutCallbackHandler = new KeyboardShortcutCallbackHandler();
mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
mNative);
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -2703,6 +2710,36 @@
lockedModifierState);
}
+ @Override
+ @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+ public void registerKeyboardSystemShortcutListener(
+ @NonNull IKeyboardSystemShortcutListener listener) {
+ super.registerKeyboardSystemShortcutListener_enforcePermission();
+ Objects.requireNonNull(listener);
+ mKeyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener,
+ Binder.getCallingPid());
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
+ public void unregisterKeyboardSystemShortcutListener(
+ @NonNull IKeyboardSystemShortcutListener listener) {
+ super.unregisterKeyboardSystemShortcutListener_enforcePermission();
+ Objects.requireNonNull(listener);
+ mKeyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener,
+ Binder.getCallingPid());
+ }
+
+ private void handleKeyboardSystemShortcutTriggered(int deviceId,
+ KeyboardSystemShortcut shortcut) {
+ InputDevice device = getInputDevice(deviceId);
+ if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+ return;
+ }
+ KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, shortcut);
+ mKeyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(deviceId, shortcut);
+ }
+
/**
* Callback interface implemented by the Window Manager.
*/
@@ -2871,6 +2908,10 @@
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ case MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED:
+ int deviceId = msg.arg1;
+ KeyboardSystemShortcut shortcut = (KeyboardSystemShortcut) msg.obj;
+ handleKeyboardSystemShortcutTriggered(deviceId, shortcut);
}
}
}
@@ -3196,6 +3237,13 @@
public int getLastUsedInputDeviceId() {
return mNative.getLastUsedInputDeviceId();
}
+
+ @Override
+ public void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, int modifierState,
+ @KeyboardSystemShortcut.SystemShortcut int shortcut) {
+ mHandler.obtainMessage(MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED, deviceId, 0,
+ new KeyboardSystemShortcut(keycodes, modifierState, shortcut)).sendToTarget();
+ }
}
@Override
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index f21fd41..3d2f951 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -24,31 +24,25 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.role.RoleManager;
-import android.content.Intent;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
+import android.hardware.input.KeyboardSystemShortcut;
import android.icu.util.ULocale;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InputDevice;
-import android.view.KeyEvent;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.policy.ModifierShortcutManager;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* Collect Keyboard metrics
@@ -66,336 +60,20 @@
@VisibleForTesting
public static final String DEFAULT_LANGUAGE_TAG = "None";
- public enum KeyboardLogEvent {
- UNSPECIFIED(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED,
- "INVALID_KEYBOARD_EVENT"),
- HOME(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME,
- "HOME"),
- RECENT_APPS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS,
- "RECENT_APPS"),
- BACK(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK,
- "BACK"),
- APP_SWITCH(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH,
- "APP_SWITCH"),
- LAUNCH_ASSISTANT(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT,
- "LAUNCH_ASSISTANT"),
- LAUNCH_VOICE_ASSISTANT(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT,
- "LAUNCH_VOICE_ASSISTANT"),
- LAUNCH_SYSTEM_SETTINGS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS,
- "LAUNCH_SYSTEM_SETTINGS"),
- TOGGLE_NOTIFICATION_PANEL(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL,
- "TOGGLE_NOTIFICATION_PANEL"),
- TOGGLE_TASKBAR(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR,
- "TOGGLE_TASKBAR"),
- TAKE_SCREENSHOT(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT,
- "TAKE_SCREENSHOT"),
- OPEN_SHORTCUT_HELPER(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER,
- "OPEN_SHORTCUT_HELPER"),
- BRIGHTNESS_UP(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP,
- "BRIGHTNESS_UP"),
- BRIGHTNESS_DOWN(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN,
- "BRIGHTNESS_DOWN"),
- KEYBOARD_BACKLIGHT_UP(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP,
- "KEYBOARD_BACKLIGHT_UP"),
- KEYBOARD_BACKLIGHT_DOWN(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN,
- "KEYBOARD_BACKLIGHT_DOWN"),
- KEYBOARD_BACKLIGHT_TOGGLE(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE,
- "KEYBOARD_BACKLIGHT_TOGGLE"),
- VOLUME_UP(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP,
- "VOLUME_UP"),
- VOLUME_DOWN(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN,
- "VOLUME_DOWN"),
- VOLUME_MUTE(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE,
- "VOLUME_MUTE"),
- ALL_APPS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS,
- "ALL_APPS"),
- LAUNCH_SEARCH(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH,
- "LAUNCH_SEARCH"),
- LANGUAGE_SWITCH(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH,
- "LANGUAGE_SWITCH"),
- ACCESSIBILITY_ALL_APPS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS,
- "ACCESSIBILITY_ALL_APPS"),
- TOGGLE_CAPS_LOCK(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK,
- "TOGGLE_CAPS_LOCK"),
- SYSTEM_MUTE(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE,
- "SYSTEM_MUTE"),
- SPLIT_SCREEN_NAVIGATION(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
- "SPLIT_SCREEN_NAVIGATION"),
-
- CHANGE_SPLITSCREEN_FOCUS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS,
- "CHANGE_SPLITSCREEN_FOCUS"),
- TRIGGER_BUG_REPORT(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
- "TRIGGER_BUG_REPORT"),
- LOCK_SCREEN(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN,
- "LOCK_SCREEN"),
- OPEN_NOTES(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES,
- "OPEN_NOTES"),
- TOGGLE_POWER(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER,
- "TOGGLE_POWER"),
- SYSTEM_NAVIGATION(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION,
- "SYSTEM_NAVIGATION"),
- SLEEP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP,
- "SLEEP"),
- WAKEUP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP,
- "WAKEUP"),
- MEDIA_KEY(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY,
- "MEDIA_KEY"),
- LAUNCH_DEFAULT_BROWSER(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER,
- "LAUNCH_DEFAULT_BROWSER"),
- LAUNCH_DEFAULT_EMAIL(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL,
- "LAUNCH_DEFAULT_EMAIL"),
- LAUNCH_DEFAULT_CONTACTS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS,
- "LAUNCH_DEFAULT_CONTACTS"),
- LAUNCH_DEFAULT_CALENDAR(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR,
- "LAUNCH_DEFAULT_CALENDAR"),
- LAUNCH_DEFAULT_CALCULATOR(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR,
- "LAUNCH_DEFAULT_CALCULATOR"),
- LAUNCH_DEFAULT_MUSIC(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC,
- "LAUNCH_DEFAULT_MUSIC"),
- LAUNCH_DEFAULT_MAPS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS,
- "LAUNCH_DEFAULT_MAPS"),
- LAUNCH_DEFAULT_MESSAGING(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING,
- "LAUNCH_DEFAULT_MESSAGING"),
- LAUNCH_DEFAULT_GALLERY(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY,
- "LAUNCH_DEFAULT_GALLERY"),
- LAUNCH_DEFAULT_FILES(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES,
- "LAUNCH_DEFAULT_FILES"),
- LAUNCH_DEFAULT_WEATHER(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER,
- "LAUNCH_DEFAULT_WEATHER"),
- LAUNCH_DEFAULT_FITNESS(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS,
- "LAUNCH_DEFAULT_FITNESS"),
- LAUNCH_APPLICATION_BY_PACKAGE_NAME(
- FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
- "LAUNCH_APPLICATION_BY_PACKAGE_NAME"),
- DESKTOP_MODE(
- FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
- "DESKTOP_MODE"),
- MULTI_WINDOW_NAVIGATION(FrameworkStatsLog
- .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION,
- "MULTIWINDOW_NAVIGATION");
-
-
- private final int mValue;
- private final String mName;
-
- private static final SparseArray<KeyboardLogEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
-
- static {
- for (KeyboardLogEvent type : KeyboardLogEvent.values()) {
- VALUE_TO_ENUM_MAP.put(type.mValue, type);
- }
- }
-
- KeyboardLogEvent(int enumValue, String enumName) {
- mValue = enumValue;
- mName = enumName;
- }
-
- public int getIntValue() {
- return mValue;
- }
-
- /**
- * Convert int value to corresponding KeyboardLogEvent enum. If can't find any matching
- * value will return {@code null}
- */
- @Nullable
- public static KeyboardLogEvent from(int value) {
- return VALUE_TO_ENUM_MAP.get(value);
- }
-
- /**
- * Find KeyboardLogEvent corresponding to volume up/down/mute key events.
- */
- @Nullable
- public static KeyboardLogEvent getVolumeEvent(int keycode) {
- switch (keycode) {
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- return VOLUME_DOWN;
- case KeyEvent.KEYCODE_VOLUME_UP:
- return VOLUME_UP;
- case KeyEvent.KEYCODE_VOLUME_MUTE:
- return VOLUME_MUTE;
- default:
- return null;
- }
- }
-
- /**
- * Find KeyboardLogEvent corresponding to brightness up/down key events.
- */
- @Nullable
- public static KeyboardLogEvent getBrightnessEvent(int keycode) {
- switch (keycode) {
- case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
- return BRIGHTNESS_DOWN;
- case KeyEvent.KEYCODE_BRIGHTNESS_UP:
- return BRIGHTNESS_UP;
- default:
- return null;
- }
- }
-
- /**
- * Find KeyboardLogEvent corresponding to intent filter category. Returns
- * {@code null if no matching event found}
- */
- @Nullable
- public static KeyboardLogEvent getLogEventFromIntent(Intent intent) {
- Intent selectorIntent = intent.getSelector();
- if (selectorIntent != null) {
- Set<String> selectorCategories = selectorIntent.getCategories();
- if (selectorCategories != null && !selectorCategories.isEmpty()) {
- for (String intentCategory : selectorCategories) {
- KeyboardLogEvent logEvent = getEventFromSelectorCategory(intentCategory);
- if (logEvent == null) {
- continue;
- }
- return logEvent;
- }
- }
- }
-
- // The shortcut may be targeting a system role rather than using an intent selector,
- // so check for that.
- String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
- if (!TextUtils.isEmpty(role)) {
- return getLogEventFromRole(role);
- }
-
- Set<String> intentCategories = intent.getCategories();
- if (intentCategories == null || intentCategories.isEmpty()
- || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
- return null;
- }
- if (intent.getComponent() == null) {
- return null;
- }
-
- // TODO(b/280423320): Add new field package name associated in the
- // KeyboardShortcutEvent atom and log it accordingly.
- return LAUNCH_APPLICATION_BY_PACKAGE_NAME;
- }
-
- @Nullable
- private static KeyboardLogEvent getEventFromSelectorCategory(String category) {
- switch (category) {
- case Intent.CATEGORY_APP_BROWSER:
- return LAUNCH_DEFAULT_BROWSER;
- case Intent.CATEGORY_APP_EMAIL:
- return LAUNCH_DEFAULT_EMAIL;
- case Intent.CATEGORY_APP_CONTACTS:
- return LAUNCH_DEFAULT_CONTACTS;
- case Intent.CATEGORY_APP_CALENDAR:
- return LAUNCH_DEFAULT_CALENDAR;
- case Intent.CATEGORY_APP_CALCULATOR:
- return LAUNCH_DEFAULT_CALCULATOR;
- case Intent.CATEGORY_APP_MUSIC:
- return LAUNCH_DEFAULT_MUSIC;
- case Intent.CATEGORY_APP_MAPS:
- return LAUNCH_DEFAULT_MAPS;
- case Intent.CATEGORY_APP_MESSAGING:
- return LAUNCH_DEFAULT_MESSAGING;
- case Intent.CATEGORY_APP_GALLERY:
- return LAUNCH_DEFAULT_GALLERY;
- case Intent.CATEGORY_APP_FILES:
- return LAUNCH_DEFAULT_FILES;
- case Intent.CATEGORY_APP_WEATHER:
- return LAUNCH_DEFAULT_WEATHER;
- case Intent.CATEGORY_APP_FITNESS:
- return LAUNCH_DEFAULT_FITNESS;
- default:
- return null;
- }
- }
-
- /**
- * Find KeyboardLogEvent corresponding to the provide system role name.
- * Returns {@code null} if no matching event found.
- */
- @Nullable
- private static KeyboardLogEvent getLogEventFromRole(String role) {
- if (RoleManager.ROLE_BROWSER.equals(role)) {
- return LAUNCH_DEFAULT_BROWSER;
- } else if (RoleManager.ROLE_SMS.equals(role)) {
- return LAUNCH_DEFAULT_MESSAGING;
- } else {
- Log.w(TAG, "Keyboard shortcut to launch "
- + role + " not supported for logging");
- return null;
- }
- }
- }
-
/**
* Log keyboard system shortcuts for the proto
* {@link com.android.os.input.KeyboardSystemsEventReported}
* defined in "stats/atoms/input/input_extension_atoms.proto"
*/
- public static void logKeyboardSystemsEventReportedAtom(@Nullable InputDevice inputDevice,
- @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes) {
- // Logging Keyboard system event only for an external HW keyboard. We should not log events
- // for virtual keyboards or internal Key events.
- if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
- return;
- }
- if (keyboardSystemEvent == null) {
- Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
- + ", modifier state = " + modifierState);
- return;
- }
+ public static void logKeyboardSystemsEventReportedAtom(@NonNull InputDevice inputDevice,
+ @NonNull KeyboardSystemShortcut keyboardSystemShortcut) {
FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
inputDevice.getVendorId(), inputDevice.getProductId(),
- keyboardSystemEvent.getIntValue(), keyCodes, modifierState,
- inputDevice.getDeviceBus());
+ keyboardSystemShortcut.getSystemShortcut(), keyboardSystemShortcut.getKeycodes(),
+ keyboardSystemShortcut.getModifierState(), inputDevice.getDeviceBus());
if (DEBUG) {
- Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
+ Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemShortcut);
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
new file mode 100644
index 0000000..092058e
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.input;
+
+import android.annotation.BinderThread;
+import android.hardware.input.IKeyboardSystemShortcutListener;
+import android.hardware.input.KeyboardSystemShortcut;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
+ * keyboard shortcut is triggered.
+ */
+final class KeyboardShortcutCallbackHandler {
+
+ private static final String TAG = "KeyboardShortcut";
+
+ // To enable these logs, run:
+ // 'adb shell setprop log.tag.KeyboardShortcutCallbackHandler DEBUG' (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // List of currently registered keyboard system shortcut listeners keyed by process pid
+ @GuardedBy("mKeyboardSystemShortcutListenerRecords")
+ private final SparseArray<KeyboardSystemShortcutListenerRecord>
+ mKeyboardSystemShortcutListenerRecords = new SparseArray<>();
+
+ public void onKeyboardSystemShortcutTriggered(int deviceId,
+ KeyboardSystemShortcut systemShortcut) {
+ if (DEBUG) {
+ Slog.d(TAG, "Keyboard system shortcut triggered, deviceId = " + deviceId
+ + ", systemShortcut = " + systemShortcut);
+ }
+
+ synchronized (mKeyboardSystemShortcutListenerRecords) {
+ for (int i = 0; i < mKeyboardSystemShortcutListenerRecords.size(); i++) {
+ mKeyboardSystemShortcutListenerRecords.valueAt(i).onKeyboardSystemShortcutTriggered(
+ deviceId, systemShortcut);
+ }
+ }
+ }
+
+ /** Register the keyboard system shortcut listener for a process. */
+ @BinderThread
+ public void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
+ int pid) {
+ synchronized (mKeyboardSystemShortcutListenerRecords) {
+ if (mKeyboardSystemShortcutListenerRecords.get(pid) != null) {
+ throw new IllegalStateException("The calling process has already registered "
+ + "a KeyboardSystemShortcutListener.");
+ }
+ KeyboardSystemShortcutListenerRecord record = new KeyboardSystemShortcutListenerRecord(
+ pid, listener);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException ex) {
+ throw new RuntimeException(ex);
+ }
+ mKeyboardSystemShortcutListenerRecords.put(pid, record);
+ }
+ }
+
+ /** Unregister the keyboard system shortcut listener for a process. */
+ @BinderThread
+ public void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
+ int pid) {
+ synchronized (mKeyboardSystemShortcutListenerRecords) {
+ KeyboardSystemShortcutListenerRecord record =
+ mKeyboardSystemShortcutListenerRecords.get(pid);
+ if (record == null) {
+ throw new IllegalStateException("The calling process has no registered "
+ + "KeyboardSystemShortcutListener.");
+ }
+ if (record.mListener.asBinder() != listener.asBinder()) {
+ throw new IllegalStateException("The calling process has a different registered "
+ + "KeyboardSystemShortcutListener.");
+ }
+ record.mListener.asBinder().unlinkToDeath(record, 0);
+ mKeyboardSystemShortcutListenerRecords.remove(pid);
+ }
+ }
+
+ private void onKeyboardSystemShortcutListenerDied(int pid) {
+ synchronized (mKeyboardSystemShortcutListenerRecords) {
+ mKeyboardSystemShortcutListenerRecords.remove(pid);
+ }
+ }
+
+ // A record of a registered keyboard system shortcut listener from one process.
+ private class KeyboardSystemShortcutListenerRecord implements IBinder.DeathRecipient {
+ public final int mPid;
+ public final IKeyboardSystemShortcutListener mListener;
+
+ KeyboardSystemShortcutListenerRecord(int pid, IKeyboardSystemShortcutListener listener) {
+ mPid = pid;
+ mListener = listener;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Keyboard system shortcut listener for pid " + mPid + " died.");
+ }
+ onKeyboardSystemShortcutListenerDied(mPid);
+ }
+
+ public void onKeyboardSystemShortcutTriggered(int deviceId, KeyboardSystemShortcut data) {
+ try {
+ mListener.onKeyboardSystemShortcutTriggered(deviceId, data.getKeycodes(),
+ data.getModifierState(), data.getSystemShortcut());
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process " + mPid
+ + " that keyboard system shortcut was triggered, assuming it died.", ex);
+ binderDied();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index a380bc1..0047ec2 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -112,7 +112,8 @@
}
@AnyThread
- void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
+ void initializeInternal(@NonNull IBinder token,
+ @NonNull IInputMethodPrivilegedOperations privilegedOperations,
@InputMethodNavButtonFlags int navigationBarFlags) {
final IInputMethod.InitParams params = new IInputMethod.InitParams();
params.token = token;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 94b1473..079b724 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -109,10 +109,6 @@
* <dd>
* If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
* </dd>
- * <dt>{@link InputMethodService#IME_INVISIBLE}</dt>
- * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
- * currently invisible.
- * </dd>
* </dl>
* <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
* {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index dba0465..e36d5bb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,7 +16,7 @@
package com.android.server.inputmethod;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -140,23 +140,26 @@
* to be switched.
*/
public boolean switchToInputMethod(@NonNull String imeId, @UserIdInt int userId) {
- return switchToInputMethod(imeId, NOT_A_SUBTYPE_ID, userId);
+ return switchToInputMethod(imeId, NOT_A_SUBTYPE_INDEX, userId);
}
/**
* Force switch to the enabled input method by {@code imeId} for the current user. If the input
- * method with {@code imeId} is not enabled or not installed, do nothing. If {@code subtypeId}
- * is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_ID}) and valid, also switches to
- * it, otherwise the system decides the most sensible default subtype to use.
+ * method with {@code imeId} is not enabled or not installed, do nothing. If
+ * {@code subtypeIndex} is also supplied (not {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}) and
+ * valid, also switches to it, otherwise the system decides the most sensible default subtype to
+ * use.
*
- * @param imeId the input method ID to be switched to
- * @param subtypeId the input method subtype ID to be switched to
- * @param userId the user ID to be queried
+ * @param imeId the input method ID to be switched to
+ * @param subtypeIndex the subtype to be switched to, as an index in the input method's array of
+ * subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if the system
+ * should decide the most sensible subtype
+ * @param userId the user ID to be queried
* @return {@code true} if the current input method was successfully switched to the input
* method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
* to be switched.
*/
- public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+ public abstract boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
@UserIdInt int userId);
/**
@@ -238,6 +241,33 @@
public abstract void removeImeSurface(int displayId);
/**
+ * Called when a non-IME-focusable overlay window being the IME layering target (e.g. a
+ * window with {@link android.view.WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} and
+ * {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flags)
+ * has changed its window visibility.
+ *
+ * @param hasVisibleOverlay whether such an overlay window exists or not
+ * @param displayId the display ID where the overlay window exists
+ */
+ public abstract void setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay, int displayId);
+
+ /**
+ * Called when the visibility of IME input target window has changed.
+ *
+ * @param imeInputTarget the window token of the IME input target window
+ * @param visibleAndNotRemoved {@code true} when the new window is made visible by
+ * {@code imeInputTarget} and the IME input target window has not
+ * been removed. The new window is considered to be visible when
+ * switching to the new visible IME input target window and
+ * starting input, or the existing input target becomes visible.
+ * In contrast, {@code false} when closing the input target, or the
+ * existing input target becomes invisible
+ * @param displayId the display for which to update the IME window status
+ */
+ public abstract void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget,
+ boolean visibleAndNotRemoved, int displayId);
+
+ /**
* Updates the IME visibility, back disposition and show IME picker status for SystemUI.
* TODO(b/189923292): Making SystemUI to be true IME icon controller vs. presenter that
* controlled by IMMS.
@@ -349,7 +379,7 @@
}
@Override
- public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+ public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
@UserIdInt int userId) {
return false;
}
@@ -389,6 +419,16 @@
public void removeImeSurface(int displayId) {
}
+ @Override
+ public void setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay,
+ int displayId) {
+ }
+
+ @Override
+ public void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget,
+ boolean visibleAndNotRemoved, int displayId) {
+ }
+
@ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8afe54e..8afbd56 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -44,16 +44,17 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
-import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -82,7 +83,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
@@ -188,7 +188,6 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.utils.PriorityDump;
-import com.android.server.wm.ImeTargetChangeListener;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -275,11 +274,6 @@
private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
- private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
-
- private static final int INVALID_SUBTYPE_HASHCODE =
- InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
-
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
private static final String HANDLER_THREAD_NAME = "android.imms";
private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
@@ -305,28 +299,6 @@
private final String[] mNonPreemptibleInputMethods;
/**
- * Whether the new Input Method Switcher menu is enabled.
- *
- * @see #shouldEnableNewInputMethodSwitcherMenu
- */
- @SharedByAllUsersField
- private final boolean mNewInputMethodSwitcherMenuEnabled;
-
- /**
- * Returns {@code true} if the new Input Method Switcher menu is enabled. This will be
- * {@code false} for watches and small screen devices.
- *
- * @param context the context to check the device configuration for.
- */
- private static boolean shouldEnableNewInputMethodSwitcherMenu(@NonNull Context context) {
- final boolean isWatch = context.getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_WATCH);
- final boolean isSmallScreen = (context.getResources().getConfiguration().screenLayout
- & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL;
- return Flags.imeSwitcherRevamp() && !isWatch && !isSmallScreen;
- }
-
- /**
* See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
*/
@SharedByAllUsersField
@@ -358,7 +330,7 @@
@UserIdInt
@BinderThread
private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) {
- return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
+ return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId;
}
/**
@@ -371,7 +343,7 @@
@UserIdInt
private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
return mConcurrentMultiUserModeEnabled
- ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+ ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId;
}
/**
@@ -387,7 +359,7 @@
final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
return mUserManagerInternal.getUserAssignedToDisplay(displayId);
}
- return mCurrentUserId;
+ return mCurrentImeUserId;
}
final Context mContext;
@@ -398,10 +370,23 @@
@NonNull
private final Handler mIoHandler;
+ /**
+ * The user ID whose IME should be used if {@link #mConcurrentMultiUserModeEnabled} is
+ * {@code false}, otherwise remains to be the initial value, which is obtained by
+ * {@link ActivityManagerInternal#getCurrentUserId()} while the device is booting up.
+ *
+ * <p>Never get confused with {@link ActivityManagerInternal#getCurrentUserId()}, which is
+ * in general useless when designing and implementing interactions between apps and IMEs.</p>
+ *
+ * <p>You can also not assume that the IME client process belongs to {@link #mCurrentImeUserId}.
+ * A most important outlier is System UI process, which always runs under
+ * {@link UserHandle#USER_SYSTEM} in all the known configurations including Headless System User
+ * Mode (HSUM).</p>
+ */
@MultiUserUnawareField
@UserIdInt
@GuardedBy("ImfLock.class")
- private int mCurrentUserId;
+ private int mCurrentImeUserId;
/** Holds all user related data */
@SharedByAllUsersField
@@ -568,7 +553,7 @@
@GuardedBy("ImfLock.class")
@Nullable
IInputMethodInvoker getCurMethodLocked() {
- return getInputMethodBindingController(mCurrentUserId).getCurMethod();
+ return getInputMethodBindingController(mCurrentImeUserId).getCurMethod();
}
/**
@@ -614,8 +599,8 @@
private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
switch (key) {
case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
- if (!mNewInputMethodSwitcherMenuEnabled) {
- if (userId == mCurrentUserId) {
+ if (!Flags.imeSwitcherRevamp()) {
+ if (userId == mCurrentImeUserId) {
mMenuController.updateKeyboardFromSettingsLocked(userId);
}
}
@@ -677,12 +662,12 @@
// sender userId can be a real user ID or USER_ALL.
final int senderUserId = pendingResult.getSendingUserId();
synchronized (ImfLock.class) {
- if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentUserId) {
+ if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentImeUserId) {
// A background user is trying to hide the dialog. Ignore.
return;
}
- final int userId = mCurrentUserId;
- if (mNewInputMethodSwitcherMenuEnabled) {
+ final int userId = mCurrentImeUserId;
+ if (Flags.imeSwitcherRevamp()) {
final var bindingController = getInputMethodBindingController(userId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
@@ -720,7 +705,7 @@
userData.mRawInputMethodMap.set(rawMethodMap);
final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
DirectBootAwareness.AUTO,
- mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+ userData.mIsUnlockingOrUnlocked.get());
final var settings = InputMethodSettings.create(methodMap, userId);
InputMethodSettingsRepository.put(userId, settings);
}
@@ -799,63 +784,70 @@
final int userId = getChangingUserId();
final var userData = getUserData(userId);
- // Instantiating InputMethodInfo requires disk I/O.
- // Do them before acquiring the lock to minimize the chances of ANR (b/340221861).
userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId));
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+
+ InputMethodInfo curIm = null;
+ String curInputMethodId = settings.getSelectedInputMethod();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
+
+ final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
+ final ArrayList<String> imesToBeDisabled = new ArrayList<>();
+ final int numImes = methodList.size();
+ for (int i = 0; i < numImes; i++) {
+ InputMethodInfo imi = methodList.get(i);
+ final String imiId = imi.getId();
+ if (imiId.equals(curInputMethodId)) {
+ curIm = imi;
+ }
+ if (mDataClearedPackages.contains(imi.getPackageName())) {
+ imesToClearAdditionalSubtypes.add(imiId);
+ }
+ int change = isPackageDisappearing(imi.getPackageName());
+ if (change == PACKAGE_PERMANENT_CHANGE) {
+ Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
+ imesToBeDisabled.add(imi.getId());
+ } else if (change == PACKAGE_UPDATING) {
+ Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
+ + imi.getComponent());
+ imesToClearAdditionalSubtypes.add(imiId);
+ }
+ }
+
+ // Clear additional subtypes as a batch operation.
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final AdditionalSubtypeMap newAdditionalSubtypeMap =
+ additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
+ final boolean additionalSubtypeChanged =
+ (newAdditionalSubtypeMap != additionalSubtypeMap);
+ if (additionalSubtypeChanged) {
+ AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+ settings.getMethodMap());
+ }
+
+ final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ newAdditionalSubtypeMap,
+ DirectBootAwareness.AUTO,
+ userData.mIsUnlockingOrUnlocked.get());
+
+ final boolean noUpdate = InputMethodMap.areSame(settings.getMethodMap(), newMethodMap);
+ if (noUpdate && imesToBeDisabled.isEmpty()) {
+ return;
+ }
+
+ // Here we start remaining tasks that need to be done with the lock (b/340221861).
synchronized (ImfLock.class) {
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
-
- InputMethodInfo curIm = null;
- String curInputMethodId = settings.getSelectedInputMethod();
- final List<InputMethodInfo> methodList = settings.getMethodList();
-
- final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
- final int numImes = methodList.size();
- for (int i = 0; i < numImes; i++) {
- InputMethodInfo imi = methodList.get(i);
- final String imiId = imi.getId();
- if (imiId.equals(curInputMethodId)) {
- curIm = imi;
- }
- if (mDataClearedPackages.contains(imi.getPackageName())) {
- imesToClearAdditionalSubtypes.add(imiId);
- }
- int change = isPackageDisappearing(imi.getPackageName());
- if (change == PACKAGE_PERMANENT_CHANGE) {
- Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
- setInputMethodEnabledLocked(imi.getId(), false, userId);
- } else if (change == PACKAGE_UPDATING) {
- Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
- + imi.getComponent());
- imesToClearAdditionalSubtypes.add(imiId);
- }
+ final int numImesToBeDisabled = imesToBeDisabled.size();
+ for (int i = 0; i < numImesToBeDisabled; ++i) {
+ setInputMethodEnabledLocked(imesToBeDisabled.get(i), false /* enabled */,
+ userId);
}
-
- // Clear additional subtypes as a batch operation.
- final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final AdditionalSubtypeMap newAdditionalSubtypeMap =
- additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
- final boolean additionalSubtypeChanged =
- (newAdditionalSubtypeMap != additionalSubtypeMap);
- if (additionalSubtypeChanged) {
- AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
- settings.getMethodMap());
- }
-
- final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
- newAdditionalSubtypeMap,
- DirectBootAwareness.AUTO,
- mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
-
- if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) {
- // No update in the actual IME map.
+ if (noUpdate) {
return;
}
-
- final InputMethodSettings newSettings =
- InputMethodSettings.create(newMethodMap, userId);
- InputMethodSettingsRepository.put(userId, newSettings);
+ InputMethodSettingsRepository.put(userId,
+ InputMethodSettings.create(newMethodMap, userId));
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
boolean changed = false;
@@ -964,37 +956,6 @@
InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
mService::onUpdateResourceOverlay);
- // Also hook up ImeTargetChangeListener.
- // TODO(b/356876005): Merge this into InputMethodManagerInternal.
- final var windowManagerInternal = mService.mWindowManagerInternal;
- windowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
- @Override
- public void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed, int displayId) {
- // Ignoring the starting window since it's ok to cover the IME target
- // window in temporary without affecting the IME visibility.
- final boolean hasOverlay = visible && !removed
- && windowType != TYPE_APPLICATION_STARTING;
- synchronized (ImfLock.class) {
- final var userId = mService.resolveImeUserIdFromDisplayIdLocked(displayId);
- mService.getUserData(userId).mVisibilityStateComputer
- .setHasVisibleImeLayeringOverlay(hasOverlay);
- }
- }
-
- @Override
- public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed, int displayId) {
- final boolean visibleAndNotRemoved = visibleRequested && !removed;
- synchronized (ImfLock.class) {
- final var userId = mService.resolveImeUserIdFromDisplayIdLocked(displayId);
- mService.getUserData(userId).mVisibilityStateComputer
- .onImeInputTargetVisibilityChanged(imeInputTarget,
- visibleAndNotRemoved);
- }
- }
- });
// Also schedule user init tasks onto an I/O thread.
initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
}
@@ -1096,13 +1057,16 @@
public void onUserUnlocking(@NonNull TargetUser user) {
// Called on ActivityManager thread. Do not block the calling thread.
final int userId = user.getUserIdentifier();
+ final var userData = mService.getUserData(userId);
+ final boolean userUnlocked = true;
+ userData.mIsUnlockingOrUnlocked.set(userUnlocked);
SecureSettingsWrapper.onUserUnlocking(userId);
+ final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
+ userUnlocked);
+ final var newSettings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, newSettings);
mService.mIoHandler.post(() -> {
- final var userData = mService.getUserData(userId);
- final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
- AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, true);
- final var newSettings = InputMethodSettings.create(methodMap, userId);
- InputMethodSettingsRepository.put(userId, newSettings);
synchronized (ImfLock.class) {
if (!mService.mSystemReady) {
return;
@@ -1146,9 +1110,12 @@
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final var rawMethodMap = queryRawInputMethodServiceMap(context, userId);
userData.mRawInputMethodMap.set(rawMethodMap);
+
+ final boolean unlocked = userManagerInternal.isUserUnlockingOrUnlocked(userId);
+ userData.mIsUnlockingOrUnlocked.set(unlocked);
final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
- DirectBootAwareness.AUTO,
- userManagerInternal.isUserUnlockingOrUnlocked(userId));
+ DirectBootAwareness.AUTO, unlocked);
+
final var settings = InputMethodSettings.create(methodMap, userId);
InputMethodSettingsRepository.put(userId, settings);
@@ -1168,16 +1135,19 @@
public void onUserStopped(@NonNull TargetUser user) {
final int userId = user.getUserIdentifier();
// Called on ActivityManager thread.
+
+ // Following operations should be trivial and fast enough, so do not dispatch them to
+ // the IO thread.
SecureSettingsWrapper.onUserStopped(userId);
- mService.mIoHandler.post(() -> {
- final var userData = mService.getUserData(userId);
- final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final var rawMethodMap = userData.mRawInputMethodMap.get();
- final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
- DirectBootAwareness.AUTO, false /* userUnlocked */);
- InputMethodSettingsRepository.put(userId,
- InputMethodSettings.create(methodMap, userId));
- });
+ final var userData = mService.getUserData(userId);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final var rawMethodMap = userData.mRawInputMethodMap.get();
+ final boolean userUnlocked = false; // Stopping a user also locks their storage.
+ userData.mIsUnlockingOrUnlocked.set(userUnlocked);
+ final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
+ DirectBootAwareness.AUTO, userUnlocked);
+ InputMethodSettingsRepository.put(userId,
+ InputMethodSettings.create(methodMap, userId));
}
}
@@ -1227,11 +1197,10 @@
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
- mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
mShowOngoingImeSwitcherForPhones = false;
- mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+ mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId();
final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
InputMethodManagerService.this);
@@ -1243,7 +1212,7 @@
: bindingControllerFactory, visibilityStateComputerFactory);
mMenuController = new InputMethodMenuController(this);
- mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
+ mMenuControllerNew = Flags.imeSwitcherRevamp()
? new InputMethodMenuControllerNew() : null;
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -1311,7 +1280,7 @@
if (DEBUG) {
Slog.i(TAG, "Default found, using " + defIm.getId());
}
- setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
+ setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_INDEX, false, userId);
}
@NonNull
@@ -1326,7 +1295,7 @@
@GuardedBy("ImfLock.class")
private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
IInputMethodClientInvoker clientToBeReset) {
- final int prevUserId = mCurrentUserId;
+ final int prevUserId = mCurrentImeUserId;
if (DEBUG) {
Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
+ " prevUserId=" + prevUserId);
@@ -1351,7 +1320,7 @@
// TODO(b/342027196): Double check if we need to always reset upon user switching.
newUserData.mLastEnabledInputMethodsStr = "";
- mCurrentUserId = newUserId;
+ mCurrentImeUserId = newUserId;
final String defaultImiId = SecureSettingsWrapper.getString(
Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
@@ -1377,6 +1346,17 @@
}
updateFromSettingsLocked(true, newUserId);
+ // Special workaround for b/356879517.
+ // KeyboardLayoutManager still expects onInputMethodSubtypeChangedForKeyboardLayoutMapping
+ // to be called back upon IME user switching, while we are actively deprecating the concept
+ // of "current IME user" at b/350386877.
+ // TODO(b/356879517): Come up with a way to avoid this special handling.
+ if (newUserData.mSubtypeForKeyboardLayoutMapping != null) {
+ final var subtypeHandleAndSubtype = newUserData.mSubtypeForKeyboardLayoutMapping;
+ mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
+ newUserId, subtypeHandleAndSubtype.first, subtypeHandleAndSubtype.second);
+ }
+
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, newUserId),
@@ -1440,13 +1420,13 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mCurrentUserId;
+ final int currentImeUserId = mCurrentImeUserId;
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
- hideStatusBarIconLocked(currentUserId);
- final var bindingController = getInputMethodBindingController(currentUserId);
+ hideStatusBarIconLocked(currentImeUserId);
+ final var bindingController = getInputMethodBindingController(currentImeUserId);
updateSystemUiLocked(bindingController.getImeWindowVis(),
- bindingController.getBackDisposition(), currentUserId);
+ bindingController.getBackDisposition(), currentImeUserId);
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
@@ -1516,27 +1496,6 @@
});
}
- /**
- * Returns true iff the caller is identified to be the current input method with the token.
- *
- * @param token the window token given to the input method when it was started
- * @param userData {@link UserData} of the calling IME process
- * @return true if and only if non-null valid token is specified
- */
- @GuardedBy("ImfLock.class")
- private boolean calledWithValidTokenLocked(@NonNull IBinder token, @NonNull UserData userData) {
- if (token == null) {
- throw new InvalidParameterException("token must not be null.");
- }
- final var bindingController = userData.mBindingController;
- if (token != bindingController.getCurToken()) {
- Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
- + " uid:" + Binder.getCallingUid() + " token:" + token);
- return false;
- }
- return true;
- }
-
@BinderThread
@Nullable
@Override
@@ -1647,7 +1606,7 @@
}
// Check if selected IME of current user supports handwriting.
- if (userId == mCurrentUserId) {
+ if (userId == mCurrentImeUserId) {
final var bindingController = getInputMethodBindingController(userId);
return bindingController.supportsStylusHandwriting()
&& (!connectionless
@@ -1678,7 +1637,7 @@
final var userData = getUserData(userId);
final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
AdditionalSubtypeMapRepository.get(userId), directBootAwareness,
- mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+ userData.mIsUnlockingOrUnlocked.get());
final var settings = InputMethodSettings.create(methodMap, userId);
// Create a copy.
final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
@@ -1853,7 +1812,7 @@
ImeTracker.PHASE_SERVER_WAIT_IME);
userData.mCurStatsToken = null;
// TODO: Make mMenuController multi-user aware
- if (mNewInputMethodSwitcherMenuEnabled) {
+ if (Flags.imeSwitcherRevamp()) {
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
mMenuController.hideInputMethodMenuLocked(userId);
@@ -2061,7 +2020,7 @@
if (deviceMethodId == null) {
visibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
} else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
- setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID,
+ setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_INDEX,
bindingController.getDeviceIdToShowIme(), userId);
selectedMethodId = deviceMethodId;
}
@@ -2593,45 +2552,36 @@
}
@BinderThread
- private void updateStatusIcon(@NonNull IBinder token, String packageName,
- @DrawableRes int iconId, @NonNull UserData userData) {
+ @GuardedBy("ImfLock.class")
+ private void updateStatusIconLocked(String packageName, @DrawableRes int iconId,
+ @NonNull UserData userData) {
final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- // To minimize app compat risk, ignore background users' request for single-user mode.
- // TODO(b/357178609): generalize the logic and remove this special rule.
- if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
- return;
- }
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- final long ident = Binder.clearCallingIdentity();
+ // To minimize app compat risk, ignore background users' request for single-user mode.
+ // TODO(b/357178609): generalize the logic and remove this special rule.
+ if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
+ return;
+ }
+ if (iconId == 0) {
+ if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
+ hideStatusBarIconLocked(userId);
+ } else if (packageName != null) {
+ if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
+ final PackageManager userAwarePackageManager =
+ getPackageManagerForUser(mContext, userId);
+ ApplicationInfo applicationInfo = null;
try {
- if (iconId == 0) {
- if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
- hideStatusBarIconLocked(userId);
- } else if (packageName != null) {
- if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
- final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, userId);
- ApplicationInfo applicationInfo = null;
- try {
- applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0));
- } catch (PackageManager.NameNotFoundException e) {
- }
- final CharSequence contentDescription = applicationInfo != null
- ? userAwarePackageManager.getApplicationLabel(applicationInfo)
- : null;
- if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
- contentDescription != null
- ? contentDescription.toString() : null);
- mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ final CharSequence contentDescription = applicationInfo != null
+ ? userAwarePackageManager.getApplicationLabel(applicationInfo)
+ : null;
+ if (mStatusBarManagerInternal != null) {
+ mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0,
+ contentDescription != null
+ ? contentDescription.toString() : null);
+ mStatusBarManagerInternal.setIconVisibility(mSlotIme, true);
}
}
}
@@ -2640,7 +2590,7 @@
private void hideStatusBarIconLocked(@UserIdInt int userId) {
// To minimize app compat risk, ignore background users' request for single-user mode.
// TODO(b/357178609): generalize the logic and remove this special rule.
- if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+ if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
return;
}
if (mStatusBarManagerInternal != null) {
@@ -2672,7 +2622,7 @@
if (!mShowOngoingImeSwitcherForPhones) return false;
// When the IME switcher dialog is shown, the IME switcher button should be hidden.
// TODO(b/305849394): Make mMenuController multi-user aware.
- final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
+ final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
? mMenuControllerNew.isShowing()
: mMenuController.getSwitchingDialogLocked() != null;
if (switcherMenuShowing) {
@@ -2688,12 +2638,10 @@
&& mWindowManagerInternal.isKeyguardSecure(userId)) {
return false;
}
- if ((visibility & InputMethodService.IME_ACTIVE) == 0
- || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+ if ((visibility & InputMethodService.IME_ACTIVE) == 0) {
return false;
}
- if (mWindowManagerInternal.isHardKeyboardAvailable()
- && !mNewInputMethodSwitcherMenuEnabled) {
+ if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
// When physical keyboard is attached, we show the ime switcher (or notification if
// NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
// exists in the IME switcher dialog. Might be OK to remove this condition once
@@ -2704,7 +2652,7 @@
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (mNewInputMethodSwitcherMenuEnabled) {
+ if (Flags.imeSwitcherRevamp()) {
// The IME switcher button should be shown when the current IME specified a
// language settings activity.
final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
@@ -2772,29 +2720,25 @@
}
@BinderThread
+ @GuardedBy("ImfLock.class")
@SuppressWarnings("deprecation")
- private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition,
+ private void setImeWindowStatusLocked(int vis, int backDisposition,
@NonNull UserData userData) {
final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId();
final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- final var bindingController = userData.mBindingController;
- // Skip update IME status when current token display is not same as focused display.
- // Note that we still need to update IME status when focusing external display
- // that does not support system decoration and fallback to show IME on default
- // display since it is intentional behavior.
- final int tokenDisplayId = bindingController.getCurTokenDisplayId();
- if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
- return;
- }
- bindingController.setImeWindowVis(vis);
- bindingController.setBackDisposition(backDisposition);
- updateSystemUiLocked(vis, backDisposition, userId);
+ final var bindingController = userData.mBindingController;
+ // Skip update IME status when current token display is not same as focused display.
+ // Note that we still need to update IME status when focusing external display
+ // that does not support system decoration and fallback to show IME on default
+ // display since it is intentional behavior.
+ final int tokenDisplayId = bindingController.getCurTokenDisplayId();
+ if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
+ return;
}
+ bindingController.setImeWindowVis(vis);
+ bindingController.setBackDisposition(backDisposition);
+ updateSystemUiLocked(vis, backDisposition, userId);
final boolean dismissImeOnBackKeyPressed;
switch (backDisposition) {
@@ -2813,19 +2757,14 @@
}
@BinderThread
- private void reportStartInput(@NonNull IBinder token, IBinder startInputToken,
- @NonNull UserData userData) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
- if (targetWindow != null) {
- mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow);
- }
- final var visibilityStateComputer = userData.mVisibilityStateComputer;
- visibilityStateComputer.setLastImeTargetWindow(targetWindow);
+ @GuardedBy("ImfLock.class")
+ private void reportStartInputLocked(IBinder startInputToken, @NonNull UserData userData) {
+ final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
+ if (targetWindow != null) {
+ mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow);
}
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ visibilityStateComputer.setLastImeTargetWindow(targetWindow);
}
@GuardedBy("ImfLock.class")
@@ -2851,7 +2790,7 @@
private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) {
// To minimize app compat risk, ignore background users' request for single-user mode.
// TODO(b/357178609): generalize the logic and remove this special rule.
- if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+ if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
return;
}
final var userData = getUserData(userId);
@@ -2864,7 +2803,7 @@
if (DEBUG) {
Slog.d(TAG, "IME window vis: " + vis
+ " active: " + (vis & InputMethodService.IME_ACTIVE)
- + " inv: " + (vis & InputMethodService.IME_INVISIBLE)
+ + " visible: " + (vis & InputMethodService.IME_VISIBLE)
+ " displayId: " + curTokenDisplayId);
}
final IBinder focusedWindowToken = userData.mImeBindingState != null
@@ -2872,8 +2811,8 @@
final Boolean windowPerceptible = focusedWindowToken != null
? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
- // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
- // all updateSystemUi happens on system privilege.
+ // TODO: Move this clearing calling identity block to setImeWindowStatusLocked after making
+ // sure all updateSystemUi happens on system privilege.
final long ident = Binder.clearCallingIdentity();
try {
if (windowPerceptible != null && !windowPerceptible) {
@@ -2886,7 +2825,7 @@
}
final var curId = bindingController.getCurId();
// TODO(b/305849394): Make mMenuController multi-user aware.
- final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
+ final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
? mMenuControllerNew.isShowing()
: mMenuController.getSwitchingDialogLocked() != null;
if (switcherMenuShowing
@@ -2908,7 +2847,7 @@
@GuardedBy("ImfLock.class")
void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
- if (!mNewInputMethodSwitcherMenuEnabled) {
+ if (!Flags.imeSwitcherRevamp()) {
mMenuController.updateKeyboardFromSettingsLocked(userId);
}
}
@@ -2976,7 +2915,7 @@
}
if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id), userId);
+ setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeIndex(id), userId);
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId);
@@ -2999,17 +2938,28 @@
? subtype : null;
final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null
? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null;
+
+ final var userData = getUserData(userId);
+
+ // A workaround for b/356879517. KeyboardLayoutManager has relied on an implementation
+ // detail that IMMS triggers this callback only for the current IME user.
+ // TODO(b/357663774): Figure out how to better handle this scenario.
+ userData.mSubtypeForKeyboardLayoutMapping =
+ Pair.create(newSubtypeHandle, normalizedSubtype);
+ if (userId != mCurrentImeUserId) {
+ return;
+ }
mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping(
userId, newSubtypeHandle, normalizedSubtype);
}
@GuardedBy("ImfLock.class")
- void setInputMethodLocked(String id, int subtypeId, @UserIdInt int userId) {
- setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT, userId);
+ void setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId) {
+ setInputMethodLocked(id, subtypeIndex, DEVICE_ID_DEFAULT, userId);
}
@GuardedBy("ImfLock.class")
- void setInputMethodLocked(String id, int subtypeId, int deviceId, @UserIdInt int userId) {
+ void setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo info = settings.getMethodMap().get(id);
if (info == null) {
@@ -3026,25 +2976,25 @@
}
final InputMethodSubtype oldSubtype = bindingController.getCurrentSubtype();
final InputMethodSubtype newSubtype;
- if (subtypeId >= 0 && subtypeId < subtypeCount) {
- newSubtype = info.getSubtypeAt(subtypeId);
+ if (subtypeIndex >= 0 && subtypeIndex < subtypeCount) {
+ newSubtype = info.getSubtypeAt(subtypeIndex);
} else {
// If subtype is null, try to find the most applicable one from
// getCurrentInputMethodSubtype.
- subtypeId = NOT_A_SUBTYPE_ID;
+ subtypeIndex = NOT_A_SUBTYPE_INDEX;
// TODO(b/347083680): The method below has questionable behaviors.
newSubtype = bindingController.getCurrentInputMethodSubtype();
if (newSubtype != null) {
for (int i = 0; i < subtypeCount; ++i) {
if (Objects.equals(newSubtype, info.getSubtypeAt(i))) {
- subtypeId = i;
+ subtypeIndex = i;
break;
}
}
}
}
if (!Objects.equals(newSubtype, oldSubtype)) {
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId);
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, true, userId);
IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
updateSystemUiLocked(bindingController.getImeWindowVis(),
@@ -3071,9 +3021,7 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- // Set a subtype to this input method.
- // subtypeId the name of a subtype which will be set.
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false, userId);
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, false, userId);
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
@@ -3768,7 +3716,7 @@
return InputBindResult.USER_SWITCHING;
}
final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mCurrentUserId, false /* enabledOnly */);
+ mCurrentImeUserId, false /* enabledOnly */);
for (int profileId : profileIdsWithDisabled) {
if (profileId == userId) {
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3815,9 +3763,9 @@
}
// Verify if caller is a background user.
- if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
+ if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) {
if (ArrayUtils.contains(
- mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+ mUserManagerInternal.getProfileIds(mCurrentImeUserId, false),
userId)) {
// cross-profile access is always allowed here to allow
// profile-switching.
@@ -4077,7 +4025,7 @@
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public boolean isInputMethodPickerShownForTest() {
synchronized (ImfLock.class) {
- return mNewInputMethodSwitcherMenuEnabled
+ return Flags.imeSwitcherRevamp()
? mMenuControllerNew.isShowing()
: mMenuController.isisInputMethodPickerShownForTestLocked();
}
@@ -4086,11 +4034,12 @@
/**
* Gets the list of Input Method Switcher Menu items and the index of the selected item.
*
- * @param items the list of input method and subtype items.
- * @param selectedImeId the ID of the selected input method.
- * @param selectedSubtypeId the ID of the selected input method subtype,
- * or {@link #NOT_A_SUBTYPE_ID} if no subtype is selected.
- * @param userId the ID of the user for which to get the menu items.
+ * @param items the list of input method and subtype items.
+ * @param selectedImeId the ID of the selected input method.
+ * @param selectedSubtypeIndex the index of the selected subtype in the input method's array of
+ * subtypes, or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if no
+ * subtype is selected.
+ * @param userId the ID of the user for which to get the menu items.
* @return the list of menu items, and the index of the selected item,
* or {@code -1} if no item is selected.
*/
@@ -4098,17 +4047,17 @@
@NonNull
private Pair<List<MenuItem>, Integer> getInputMethodPickerItems(
@NonNull List<ImeSubtypeListItem> items, @Nullable String selectedImeId,
- int selectedSubtypeId, @UserIdInt int userId) {
+ int selectedSubtypeIndex, @UserIdInt int userId) {
final var bindingController = getInputMethodBindingController(userId);
final var settings = InputMethodSettingsRepository.get(userId);
- if (selectedSubtypeId == NOT_A_SUBTYPE_ID) {
+ if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
// TODO(b/351124299): Check if this fallback logic is still necessary.
final var curSubtype = bindingController.getCurrentInputMethodSubtype();
if (curSubtype != null) {
final var curMethodId = bindingController.getSelectedMethodId();
final var curImi = settings.getMethodMap().get(curMethodId);
- selectedSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+ selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
curImi, curSubtype.hashCode());
}
}
@@ -4123,35 +4072,24 @@
final var item = items.get(i);
final var imeId = item.mImi.getId();
if (imeId.equals(selectedImeId)) {
- final int subtypeId = item.mSubtypeId;
+ final int subtypeIndex = item.mSubtypeIndex;
// Check if this is the selected IME-subtype pair.
- if ((subtypeId == 0 && selectedSubtypeId == NOT_A_SUBTYPE_ID)
- || subtypeId == NOT_A_SUBTYPE_ID
- || subtypeId == selectedSubtypeId) {
+ if ((subtypeIndex == 0 && selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX)
+ || subtypeIndex == NOT_A_SUBTYPE_INDEX
+ || subtypeIndex == selectedSubtypeIndex) {
selectedIndex = i;
}
}
final boolean hasHeader = !imeId.equals(prevImeId);
final boolean hasDivider = hasHeader && prevImeId != null;
prevImeId = imeId;
- menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi, item.mSubtypeId,
- hasHeader, hasDivider));
+ menuItems.add(new MenuItem(item.mImeName, item.mSubtypeName, item.mImi,
+ item.mSubtypeIndex, hasHeader, hasDivider));
}
return new Pair<>(menuItems, selectedIndex);
}
- @BinderThread
- private void onImeSwitchButtonClickFromClient(@NonNull IBinder token, int displayId,
- @NonNull UserData userData) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- onImeSwitchButtonClickLocked(token, displayId, userData);
- }
- }
-
@IInputMethodManagerImpl.PermissionVerified(allOf = {
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
Manifest.permission.WRITE_SECURE_SETTINGS})
@@ -4160,13 +4098,8 @@
synchronized (ImfLock.class) {
final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
final var userData = getUserData(userId);
- final var bindingController = userData.mBindingController;
- final var curToken = bindingController.getCurToken();
- if (curToken == null) {
- return;
- }
- onImeSwitchButtonClickLocked(curToken, displayId, userData);
+ onImeSwitchButtonClickLocked(displayId, userData);
}
}
@@ -4174,17 +4107,16 @@
* Handles a click on the IME switch button. Depending on the number of enabled IME subtypes,
* this will either switch to the next IME/subtype, or show the input method picker dialog.
*
- * @param token The token identifying the input method that triggered this.
* @param displayId The ID of the display where the input method picker dialog should be shown.
* @param userData The data of the user for which to switch IMEs or show the picker dialog.
*/
+ @BinderThread
@GuardedBy("ImfLock.class")
- private void onImeSwitchButtonClickLocked(@NonNull IBinder token, int displayId,
- @NonNull UserData userData) {
+ private void onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData) {
final int userId = userData.mUserId;
final var settings = InputMethodSettingsRepository.get(userId);
if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, settings)) {
- switchToNextInputMethodLocked(token, false /* onlyCurrentIme */, userData);
+ switchToNextInputMethodLocked(false /* onlyCurrentIme */, userData);
} else {
showInputMethodPickerFromSystem(
InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId);
@@ -4198,143 +4130,102 @@
}
@BinderThread
- private void setInputMethod(@NonNull IBinder token, String id, @NonNull UserData userData) {
- final int callingUid = Binder.getCallingUid();
- final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final InputMethodInfo imi = settings.getMethodMap().get(id);
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, settings)) {
- throw getExceptionForUnknownImeId(id);
- }
- setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID, userId);
- }
- }
-
- @BinderThread
- private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
- InputMethodSubtype subtype, @NonNull UserData userData) {
- final int callingUid = Binder.getCallingUid();
- final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final InputMethodInfo imi = settings.getMethodMap().get(id);
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, settings)) {
- throw getExceptionForUnknownImeId(id);
- }
- if (subtype != null) {
- setInputMethodWithSubtypeIdLocked(token, id,
- SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId);
- } else {
- setInputMethod(token, id, userData);
- }
- }
- }
-
- @BinderThread
- private boolean switchToPreviousInputMethod(@NonNull IBinder token,
+ @GuardedBy("ImfLock.class")
+ private void setInputMethodAndSubtypeLocked(String id, @Nullable InputMethodSubtype subtype,
@NonNull UserData userData) {
+ final int callingUid = Binder.getCallingUid();
final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return false;
- }
- final var bindingController = userData.mBindingController;
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
- final InputMethodInfo lastImi;
- if (lastIme != null) {
- lastImi = settings.getMethodMap().get(lastIme.first);
- } else {
- lastImi = null;
- }
- final var currentSubtype = bindingController.getCurrentSubtype();
- String targetLastImiId = null;
- int subtypeId = NOT_A_SUBTYPE_ID;
- if (lastIme != null && lastImi != null) {
- final boolean imiIdIsSame = lastImi.getId().equals(
- bindingController.getSelectedMethodId());
- final int lastSubtypeHash = Integer.parseInt(lastIme.second);
- final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_ID
- : currentSubtype.hashCode();
- // If the last IME is the same as the current IME and the last subtype is not
- // defined, there is no need to switch to the last IME.
- if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
- targetLastImiId = lastIme.first;
- subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
- }
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
+ if (imi == null || !canCallerAccessInputMethod(
+ imi.getPackageName(), callingUid, userId, settings)) {
+ throw getExceptionForUnknownImeId(id);
+ }
+ final int subtypeIndex = subtype != null
+ ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+ : NOT_A_SUBTYPE_INDEX;
+ setInputMethodWithSubtypeIndexLocked(id, subtypeIndex, userId);
+ }
- if (TextUtils.isEmpty(targetLastImiId)
- && !InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
- // This is a safety net. If the currentSubtype can't be added to the history
- // and the framework couldn't find the last ime, we will make the last ime be
- // the most applicable enabled keyboard subtype of the system imes.
- final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
- if (enabled != null) {
- final int enabledCount = enabled.size();
- final String locale;
- if (currentSubtype != null
- && !TextUtils.isEmpty(currentSubtype.getLocale())) {
- locale = currentSubtype.getLocale();
- } else {
- locale = SystemLocaleWrapper.get(userId).get(0).toString();
- }
- for (int i = 0; i < enabledCount; ++i) {
- final InputMethodInfo imi = enabled.get(i);
- if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
- InputMethodSubtype keyboardSubtype =
- SubtypeUtils.findLastResortApplicableSubtype(
- SubtypeUtils.getSubtypes(imi),
- SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
- if (keyboardSubtype != null) {
- targetLastImiId = imi.getId();
- subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- keyboardSubtype.hashCode());
- if (keyboardSubtype.getLocale().equals(locale)) {
- break;
- }
- }
+ @BinderThread
+ @GuardedBy("ImfLock.class")
+ private boolean switchToPreviousInputMethodLocked(@NonNull UserData userData) {
+ final int userId = userData.mUserId;
+ final var bindingController = userData.mBindingController;
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
+ final InputMethodInfo lastImi;
+ if (lastIme != null) {
+ lastImi = settings.getMethodMap().get(lastIme.first);
+ } else {
+ lastImi = null;
+ }
+ final var currentSubtype = bindingController.getCurrentSubtype();
+ String targetLastImiId = null;
+ int subtypeIndex = NOT_A_SUBTYPE_INDEX;
+ if (lastIme != null && lastImi != null) {
+ final boolean imiIdIsSame = lastImi.getId().equals(
+ bindingController.getSelectedMethodId());
+ final int lastSubtypeHash = Integer.parseInt(lastIme.second);
+ final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_INDEX
+ : currentSubtype.hashCode();
+ // If the last IME is the same as the current IME and the last subtype is not
+ // defined, there is no need to switch to the last IME.
+ if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
+ targetLastImiId = lastIme.first;
+ subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi, lastSubtypeHash);
+ }
+ }
+
+ if (TextUtils.isEmpty(targetLastImiId)
+ && !InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
+ // This is a safety net. If the currentSubtype can't be added to the history
+ // and the framework couldn't find the last ime, we will make the last ime be
+ // the most applicable enabled keyboard subtype of the system imes.
+ final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
+ final int enabledCount = enabled.size();
+ final String locale;
+ if (currentSubtype != null
+ && !TextUtils.isEmpty(currentSubtype.getLocale())) {
+ locale = currentSubtype.getLocale();
+ } else {
+ locale = SystemLocaleWrapper.get(userId).get(0).toString();
+ }
+ for (int i = 0; i < enabledCount; ++i) {
+ final InputMethodInfo imi = enabled.get(i);
+ if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
+ InputMethodSubtype keyboardSubtype =
+ SubtypeUtils.findLastResortApplicableSubtype(
+ SubtypeUtils.getSubtypes(imi),
+ SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
+ if (keyboardSubtype != null) {
+ targetLastImiId = imi.getId();
+ subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
+ keyboardSubtype.hashCode());
+ if (keyboardSubtype.getLocale().equals(locale)) {
+ break;
}
}
}
}
+ }
- if (!TextUtils.isEmpty(targetLastImiId)) {
- if (DEBUG) {
- Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
- + ", from: " + bindingController.getSelectedMethodId() + ", "
- + subtypeId);
- }
- setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId, userId);
- return true;
- } else {
- return false;
+ if (!TextUtils.isEmpty(targetLastImiId)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
+ + ", from: " + bindingController.getSelectedMethodId() + ", "
+ + subtypeIndex);
}
+ setInputMethodWithSubtypeIndexLocked(targetLastImiId, subtypeIndex, userId);
+ return true;
+ } else {
+ return false;
}
}
@BinderThread
- private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme,
- @NonNull UserData userData) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return false;
- }
- return switchToNextInputMethodLocked(token, onlyCurrentIme, userData);
- }
- }
-
@GuardedBy("ImfLock.class")
- private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme,
+ private boolean switchToNextInputMethodLocked(boolean onlyCurrentIme,
@NonNull UserData userData) {
final var bindingController = userData.mBindingController;
final var currentImi = bindingController.getSelectedMethod();
@@ -4345,26 +4236,21 @@
if (nextSubtype == null) {
return false;
}
- setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
- nextSubtype.mSubtypeId, userData.mUserId);
+ setInputMethodWithSubtypeIndexLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeIndex,
+ userData.mUserId);
return true;
}
@BinderThread
- private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token,
- @NonNull UserData userData) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return false;
- }
- final var bindingController = userData.mBindingController;
- final var currentImi = bindingController.getSelectedMethod();
- final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
- .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
- bindingController.getCurrentSubtype(),
- MODE_AUTO, true /* forward */);
- return nextSubtype != null;
- }
+ @GuardedBy("ImfLock.class")
+ private boolean shouldOfferSwitchingToNextInputMethodLocked(@NonNull UserData userData) {
+ final var bindingController = userData.mBindingController;
+ final var currentImi = bindingController.getSelectedMethod();
+ final ImeSubtypeListItem nextSubtype = userData.mSwitchingController
+ .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi,
+ bindingController.getCurrentSubtype(),
+ MODE_AUTO, true /* forward */);
+ return nextSubtype != null;
}
@Override
@@ -4416,7 +4302,7 @@
try {
final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
- mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
+ userData.mIsUnlockingOrUnlocked.get());
final var newSettings = InputMethodSettings.create(methodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
@@ -4567,7 +4453,7 @@
mStylusIds.add(deviceId);
// a new Stylus is detected. If IME supports handwriting, and we don't have
// handwriting initialized, lets do it now.
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final var bindingController = getInputMethodBindingController(mCurrentImeUserId);
if (!mHwController.getCurrentRequestId().isPresent()
&& bindingController.supportsStylusHandwriting()) {
scheduleResetStylusHandwriting();
@@ -4756,7 +4642,7 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
- final int userId = mCurrentUserId;
+ final int userId = mCurrentImeUserId;
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
final var visibilityStateComputer = userData.mVisibilityStateComputer;
@@ -4783,7 +4669,7 @@
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
- if (!mNewInputMethodSwitcherMenuEnabled) {
+ if (!Flags.imeSwitcherRevamp()) {
proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
mMenuController.getShowImeWithHardKeyboard());
}
@@ -4793,95 +4679,61 @@
}
@BinderThread
- private void notifyUserAction(@NonNull IBinder token, @NonNull UserData userData) {
+ @GuardedBy("ImfLock.class")
+ private void notifyUserActionLocked(@NonNull UserData userData) {
if (DEBUG) {
Slog.d(TAG, "Got the notification of a user action.");
}
- synchronized (ImfLock.class) {
- final var bindingController = userData.mBindingController;
- if (bindingController.getCurToken() != token) {
- if (DEBUG) {
- Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
- + " active.");
- }
- return;
- }
- final InputMethodInfo imi = bindingController.getSelectedMethod();
- if (imi != null) {
- userData.mSwitchingController.onUserActionLocked(imi,
- bindingController.getCurrentSubtype());
- }
+ final var bindingController = userData.mBindingController;
+ final InputMethodInfo imi = bindingController.getSelectedMethod();
+ if (imi != null) {
+ userData.mSwitchingController.onUserActionLocked(imi,
+ bindingController.getCurrentSubtype());
}
}
@BinderThread
- private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
+ @GuardedBy("ImfLock.class")
+ private void applyImeVisibilityLocked(IBinder windowToken, boolean setVisible,
@NonNull ImeTracker.Token statsToken, @NonNull UserData userData) {
try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibilityLocked");
final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
- return;
- }
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
- final var visibilityStateComputer = userData.mVisibilityStateComputer;
- final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(
- windowToken, userId);
- mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
- setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
- SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
- }
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(
+ windowToken, userId);
+ mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
+ setVisible ? STATE_SHOW_IME : STATE_HIDE_IME,
+ SoftInputShowHideReason.NOT_SET /* ignore reason */, userId);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@BinderThread
- private void resetStylusHandwriting(int requestId) {
- synchronized (ImfLock.class) {
- final OptionalInt curRequest = mHwController.getCurrentRequestId();
- if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
- Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
- + requestId);
- }
- removeVirtualStylusIdForTestSessionLocked();
- scheduleResetStylusHandwriting();
+ @GuardedBy("ImfLock.class")
+ private void resetStylusHandwritingLocked(int requestId) {
+ final OptionalInt curRequest = mHwController.getCurrentRequestId();
+ if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) {
+ Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: "
+ + requestId);
}
+ removeVirtualStylusIdForTestSessionLocked();
+ scheduleResetStylusHandwriting();
}
@GuardedBy("ImfLock.class")
- private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId,
+ private void setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex,
@UserIdInt int userId) {
- final var bindingController = getInputMethodBindingController(userId);
- if (token == null) {
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(
- "Using null token requires permission "
- + android.Manifest.permission.WRITE_SECURE_SETTINGS);
- }
- } else if (bindingController.getCurToken() != token) {
- Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
- + " token: " + token);
- return;
- } else {
- // Called with current IME's token.
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (settings.getMethodMap().get(id) != null
- && settings.getEnabledInputMethodListWithFilter(
- (info) -> info.getId().equals(id)).isEmpty()) {
- throw new IllegalStateException("Requested IME is not enabled: " + id);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (settings.getMethodMap().get(id) != null
+ && settings.getEnabledInputMethodListWithFilter(
+ (info) -> info.getId().equals(id)).isEmpty()) {
+ throw new IllegalStateException("Requested IME is not enabled: " + id);
}
-
final long ident = Binder.clearCallingIdentity();
try {
- setInputMethodLocked(id, subtypeId, userId);
+ setInputMethodLocked(id, subtypeIndex, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4916,72 +4768,36 @@
}
@BinderThread
- private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
+ @GuardedBy("ImfLock.class")
+ private void hideMySoftInputLocked(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
@NonNull UserData userData) {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
- final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
- return;
- }
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
- final long ident = Binder.clearCallingIdentity();
- try {
- if (Flags.refactorInsetsController()) {
- userData.mCurClient.mClient.setImeVisibility(false, statsToken);
- // TODO we will loose the flags here
- setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
- } else {
- final var visibilityStateComputer = userData.mVisibilityStateComputer;
- hideCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
- statsToken, flags, null /* resultReceiver */, reason, userId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ final int userId = userData.mUserId;
+ if (Flags.refactorInsetsController()) {
+ userData.mCurClient.mClient.setImeVisibility(false, statsToken);
+ // TODO we will loose the flags here
+ setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+ } else {
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ hideCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
+ statsToken, flags, null /* resultReceiver */, reason, userId);
}
}
@BinderThread
- private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
+ @GuardedBy("ImfLock.class")
+ private void showMySoftInputLocked(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
@NonNull UserData userData) {
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
- final int userId = userData.mUserId;
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
- return;
- }
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
- final long ident = Binder.clearCallingIdentity();
- try {
- if (Flags.refactorInsetsController()) {
- userData.mCurClient.mClient.setImeVisibility(true, statsToken);
- setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
- } else {
- final var visibilityStateComputer = userData.mVisibilityStateComputer;
- showCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
- statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
- null /* resultReceiver */, reason, userId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- } finally {
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ final int userId = userData.mUserId;
+ if (Flags.refactorInsetsController()) {
+ userData.mCurClient.mClient.setImeVisibility(true, statsToken);
+ setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
+ } else {
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ showCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(),
+ statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
+ null /* resultReceiver */, reason, userId);
}
}
@@ -5078,7 +4894,8 @@
final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
&& mWindowManagerInternal.isKeyguardSecure(userId);
final String lastInputMethodId = settings.getSelectedInputMethod();
- int lastInputMethodSubtypeId = settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+ final int lastInputMethodSubtypeIndex =
+ settings.getSelectedInputMethodSubtypeIndex(lastInputMethodId);
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
@@ -5092,30 +4909,30 @@
return;
}
- if (mNewInputMethodSwitcherMenuEnabled) {
+ if (Flags.imeSwitcherRevamp()) {
if (DEBUG) {
Slog.v(TAG, "Show IME switcher menu,"
+ " showAuxSubtypes=" + showAuxSubtypes
+ " displayId=" + displayId
+ " preferredInputMethodId=" + lastInputMethodId
- + " preferredInputMethodSubtypeId=" + lastInputMethodSubtypeId);
+ + " preferredInputMethodSubtypeIndex=" + lastInputMethodSubtypeIndex);
}
final var itemsAndIndex = getInputMethodPickerItems(imList,
- lastInputMethodId, lastInputMethodSubtypeId, userId);
+ lastInputMethodId, lastInputMethodSubtypeIndex, userId);
final var menuItems = itemsAndIndex.first;
final int selectedIndex = itemsAndIndex.second;
if (selectedIndex == -1) {
Slog.w(TAG, "Switching menu shown with no item selected"
+ ", IME id: " + lastInputMethodId
- + ", subtype index: " + lastInputMethodSubtypeId);
+ + ", subtype index: " + lastInputMethodSubtypeIndex);
}
mMenuControllerNew.show(menuItems, selectedIndex, displayId, userId);
} else {
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
- lastInputMethodId, lastInputMethodSubtypeId, imList, userId);
+ lastInputMethodId, lastInputMethodSubtypeIndex, imList, userId);
}
}
@@ -5144,7 +4961,7 @@
case MSG_REMOVE_IME_SURFACE: {
synchronized (ImfLock.class) {
// TODO(b/305849394): Needs to figure out what to do where for background users.
- final int userId = mCurrentUserId;
+ final int userId = mCurrentImeUserId;
final var userData = getUserData(userId);
try {
if (userData.mEnabledSession != null
@@ -5182,7 +4999,7 @@
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
- if (!mNewInputMethodSwitcherMenuEnabled) {
+ if (!Flags.imeSwitcherRevamp()) {
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
}
synchronized (ImfLock.class) {
@@ -5210,7 +5027,8 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final var bindingController =
+ getInputMethodBindingController(mCurrentImeUserId);
if (bindingController.supportsStylusHandwriting()
&& bindingController.getCurMethod() != null
&& hasSupportedStylusLocked()) {
@@ -5284,7 +5102,9 @@
private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { }
@BinderThread
- private void onStylusHandwritingReady(int requestId, int pid, @NonNull UserData userData) {
+ @GuardedBy("ImfLock.class")
+ private void onStylusHandwritingReadyLocked(int requestId, int pid,
+ @NonNull UserData userData) {
mHandler.obtainMessage(MSG_START_HANDWRITING,
new HandwritingRequest(requestId, pid, userData)).sendToTarget();
}
@@ -5292,7 +5112,7 @@
private void handleSetInteractive(final boolean interactive) {
synchronized (ImfLock.class) {
// TODO(b/305849394): Support multiple IMEs.
- final int userId = mCurrentUserId;
+ final int userId = mCurrentImeUserId;
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
mIsInteractive = interactive;
@@ -5632,7 +5452,7 @@
}
@GuardedBy("ImfLock.class")
- private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
+ private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex,
boolean setSubtypeOnly, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var bindingController = getInputMethodBindingController(userId);
@@ -5642,12 +5462,12 @@
// Set Subtype here
final int newSubtypeHashcode;
final InputMethodSubtype newSubtype;
- if (imi == null || subtypeId < 0) {
+ if (imi == null || subtypeIndex < 0) {
newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE;
newSubtype = null;
} else {
- if (subtypeId < imi.getSubtypeCount()) {
- InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
+ if (subtypeIndex < imi.getSubtypeCount()) {
+ InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex);
newSubtypeHashcode = subtype.hashCode();
newSubtype = subtype;
} else {
@@ -5683,20 +5503,20 @@
settings.putSelectedDefaultDeviceInputMethod(null);
InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
- int lastSubtypeId = NOT_A_SUBTYPE_ID;
+ int lastSubtypeIndex = NOT_A_SUBTYPE_INDEX;
// newDefaultIme is empty when there is no candidate for the selected IME.
if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
if (subtypeHashCode != null) {
try {
- lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+ lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi,
Integer.parseInt(subtypeHashCode));
} catch (NumberFormatException e) {
Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
}
}
}
- setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false, userId);
+ setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeIndex, false, userId);
}
/**
@@ -5720,14 +5540,14 @@
}
@GuardedBy("ImfLock.class")
- private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
+ private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeIndex,
@UserIdInt int userId) {
final var settings = InputMethodSettingsRepository.get(userId);
final var enabledImes = settings.getEnabledInputMethodList();
if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) {
return false; // IME is not found or not enabled.
}
- setInputMethodLocked(imeId, subtypeId, userId);
+ setInputMethodLocked(imeId, subtypeIndex, userId);
return true;
}
@@ -5783,8 +5603,8 @@
return;
}
- final var nextSubtype = nextItem.mSubtypeId > NOT_A_SUBTYPE_ID
- ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeId) : null;
+ final var nextSubtype = nextItem.mSubtypeIndex > NOT_A_SUBTYPE_INDEX
+ ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeIndex) : null;
nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype);
} else {
final InputMethodSubtypeHandle currentSubtypeHandle =
@@ -5803,7 +5623,7 @@
final int subtypeCount = nextImi.getSubtypeCount();
if (subtypeCount == 0) {
if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
- setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID, userId);
+ setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_INDEX, userId);
}
return;
}
@@ -5881,10 +5701,10 @@
}
@Override
- public boolean switchToInputMethod(@NonNull String imeId, int subtypeId,
+ public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
- return switchToInputMethodLocked(imeId, subtypeId, userId);
+ return switchToInputMethodLocked(imeId, subtypeIndex, userId);
}
}
@@ -5966,7 +5786,7 @@
final var visibilityStateComputer = userData.mVisibilityStateComputer;
if (visibilityStateComputer.getLastImeTargetWindow()
!= userData.mImeBindingState.mFocusedWindow) {
- if (mNewInputMethodSwitcherMenuEnabled) {
+ if (Flags.imeSwitcherRevamp()) {
final var bindingController = getInputMethodBindingController(userId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
@@ -5982,6 +5802,25 @@
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
+ @Override
+ public void setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay, int displayId) {
+ synchronized (ImfLock.class) {
+ final var userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ getUserData(userId).mVisibilityStateComputer.setHasVisibleImeLayeringOverlay(
+ hasVisibleOverlay);
+ }
+ }
+
+ @Override
+ public void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget,
+ boolean visibleAndNotRemoved, int displayId) {
+ synchronized (ImfLock.class) {
+ final var userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ getUserData(userId).mVisibilityStateComputer.onImeInputTargetVisibilityChanged(
+ imeInputTarget, visibleAndNotRemoved);
+ }
+ }
+
@ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
@@ -6094,71 +5933,54 @@
}
@BinderThread
- private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
- @Nullable Uri contentUri, @Nullable String packageName, @NonNull UserData userData) {
- if (token == null) {
- throw new NullPointerException("token");
- }
- if (packageName == null) {
- throw new NullPointerException("packageName");
- }
- if (contentUri == null) {
- throw new NullPointerException("contentUri");
- }
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ private IInputContentUriToken createInputContentUriTokenLocked(@NonNull Uri contentUri,
+ @NonNull String packageName, @NonNull UserData userData) {
+ Objects.requireNonNull(packageName, "packageName must not be null");
+ Objects.requireNonNull(contentUri, "contentUri must not be null");
final String contentUriScheme = contentUri.getScheme();
if (!"content".equals(contentUriScheme)) {
throw new InvalidParameterException("contentUri must have content scheme");
}
- synchronized (ImfLock.class) {
- final int uid = Binder.getCallingUid();
- final var bindingController = userData.mBindingController;
- if (bindingController.getSelectedMethodId() == null) {
- return null;
- }
- if (bindingController.getCurToken() != token) {
- Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken="
- + bindingController.getCurToken() + " token=" + token);
- return null;
- }
- // We cannot simply distinguish a bad IME that reports an arbitrary package name from
- // an unfortunate IME whose internal state is already obsolete due to the asynchronous
- // nature of our system. Let's compare it with our internal record.
- final var curPackageName = userData.mCurEditorInfo != null
- ? userData.mCurEditorInfo.packageName : null;
- if (!TextUtils.equals(curPackageName, packageName)) {
- Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
- + curPackageName + " packageName=" + packageName);
- return null;
- }
- // This user ID can never be spoofed.
- final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
- // This user ID may be invalid if "contentUri" embedded an invalid user ID.
- final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
- userData.mUserId);
- final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
- // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
- // actually has the right to grant a read permission for "contentUriWithoutUserId" that
- // is claimed to belong to "contentUriOwnerUserId". For example, specifying random
- // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
- // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
- // actually allowed to "uid", which is guaranteed to be the IME's one.
- return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
- packageName, contentUriOwnerUserId, appUserId);
+ final int uid = Binder.getCallingUid();
+ final var bindingController = userData.mBindingController;
+ if (bindingController.getSelectedMethodId() == null) {
+ return null;
}
+ // We cannot simply distinguish a bad IME that reports an arbitrary package name from
+ // an unfortunate IME whose internal state is already obsolete due to the asynchronous
+ // nature of our system. Let's compare it with our internal record.
+ final var curPackageName = userData.mCurEditorInfo != null
+ ? userData.mCurEditorInfo.packageName : null;
+ if (!TextUtils.equals(curPackageName, packageName)) {
+ Slog.e(TAG, "Ignoring createInputContentUriTokenLocked mCurEditorInfo.packageName="
+ + curPackageName + " packageName=" + packageName);
+ return null;
+ }
+ // This user ID can never be spoofed.
+ final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
+ // This user ID may be invalid if "contentUri" embedded an invalid user ID.
+ final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
+ userData.mUserId);
+ final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
+ // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
+ // actually has the right to grant a read permission for "contentUriWithoutUserId" that
+ // is claimed to belong to "contentUriOwnerUserId". For example, specifying random
+ // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
+ // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
+ // actually allowed to "uid", which is guaranteed to be the IME's one.
+ return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
+ packageName, contentUriOwnerUserId, appUserId);
}
@BinderThread
- private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen,
- @NonNull UserData userData) {
- synchronized (ImfLock.class) {
- if (!calledWithValidTokenLocked(token, userData)) {
- return;
- }
- if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
- userData.mInFullscreenMode = fullscreen;
- userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
- }
+ @GuardedBy("ImfLock.class")
+ private void reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData) {
+ if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
+ userData.mInFullscreenMode = fullscreen;
+ userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
}
}
@@ -6249,7 +6071,7 @@
final Printer p = new PrintWriterPrinter(pw);
synchronized (ImfLock.class) {
- final int userId = mCurrentUserId;
+ final int userId = mCurrentImeUserId;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var userData = getUserData(userId);
p.println("Current Input Method Manager state:");
@@ -6281,7 +6103,7 @@
};
mClientController.forAllClients(clientControllerDump);
final var bindingController = userData.mBindingController;
- p.println(" mCurrentUserId=" + userData.mUserId);
+ p.println(" mCurrentImeUserId=" + userData.mUserId);
p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
client = userData.mCurClient;
p.println(" mCurClient=" + client + " mCurSeq="
@@ -6299,6 +6121,7 @@
@SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump =
u -> {
p.println(" mUserId=" + u.mUserId);
+ p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get());
p.println(" hasMainConnection="
+ u.mBindingController.hasMainConnection());
p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound());
@@ -6322,7 +6145,7 @@
};
mUserDataRepository.forAllUserData(userDataDump);
- if (mNewInputMethodSwitcherMenuEnabled) {
+ if (Flags.imeSwitcherRevamp()) {
p.println(" menuControllerNew:");
mMenuControllerNew.dump(p, " ");
} else {
@@ -6373,7 +6196,7 @@
p.println("No input method client.");
}
synchronized (ImfLock.class) {
- final int userId = mCurrentUserId;
+ final int userId = mCurrentImeUserId;
final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindowClient != null
&& client != userData.mImeBindingState.mFocusedWindowClient) {
@@ -6604,7 +6427,7 @@
}
final int[] userIds;
synchronized (ImfLock.class) {
- userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentUserId,
+ userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentImeUserId,
shellCommand.getErrPrintWriter());
}
try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
@@ -6650,7 +6473,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mCurrentUserId, shellCommand.getErrPrintWriter());
+ mCurrentImeUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6745,13 +6568,13 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mCurrentUserId, shellCommand.getErrPrintWriter());
+ mCurrentImeUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
}
boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId,
- NOT_A_SUBTYPE_ID, userId);
+ NOT_A_SUBTYPE_INDEX, userId);
if (failedToSelectUnknownIme) {
error.print("Unknown input method ");
error.print(imeId);
@@ -6765,6 +6588,28 @@
out.print(imeId);
out.print(" selected for user #");
out.println(userId);
+
+ // Workaround for b/354782333.
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(userId);
+ final var bindingController = getInputMethodBindingController(userId);
+ final int deviceId = bindingController.getDeviceIdToShowIme();
+ final String settingsValue;
+ if (deviceId == DEVICE_ID_DEFAULT) {
+ settingsValue = settings.getSelectedInputMethod();
+ } else {
+ settingsValue = settings.getSelectedDefaultDeviceInputMethod();
+ }
+ if (!TextUtils.equals(settingsValue, imeId)) {
+ Slog.w(TAG, "DEFAULT_INPUT_METHOD=" + settingsValue
+ + " is not updated. Fixing it up to " + imeId
+ + " See b/354782333.");
+ if (deviceId == DEVICE_ID_DEFAULT) {
+ settings.putSelectedInputMethod(imeId);
+ } else {
+ settings.putSelectedDefaultDeviceInputMethod(imeId);
+ }
+ }
}
hasFailed |= failedToSelectUnknownIme;
}
@@ -6786,7 +6631,7 @@
synchronized (ImfLock.class) {
try (PrintWriter out = shellCommand.getOutPrintWriter()) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mCurrentUserId, shellCommand.getErrPrintWriter());
+ mCurrentImeUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6951,13 +6796,14 @@
private static final class InputMethodPrivilegedOperationsImpl
extends IInputMethodPrivilegedOperations.Stub {
+ @NonNull
private final InputMethodManagerService mImms;
@NonNull
private final IBinder mToken;
@NonNull
private final UserData mUserData;
- InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
+ InputMethodPrivilegedOperationsImpl(@NonNull InputMethodManagerService imms,
@NonNull IBinder token, @NonNull UserData userData) {
mImms = imms;
mToken = token;
@@ -6967,19 +6813,34 @@
@BinderThread
@Override
public void setImeWindowStatusAsync(int vis, int backDisposition) {
- mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.setImeWindowStatusLocked(vis, backDisposition, mUserData);
+ }
}
@BinderThread
@Override
public void reportStartInputAsync(IBinder startInputToken) {
- mImms.reportStartInput(mToken, startInputToken, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.reportStartInputLocked(startInputToken, mUserData);
+ }
}
@BinderThread
@Override
public void setHandwritingSurfaceNotTouchable(boolean notTouchable) {
- mImms.mHwController.setNotTouchable(notTouchable);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.mHwController.setNotTouchable(notTouchable);
+ }
}
@BinderThread
@@ -6988,8 +6849,14 @@
AndroidFuture future /* T=IBinder */) {
@SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
try {
- typedFuture.complete(mImms.createInputContentUriToken(
- mToken, contentUri, packageName, mUserData).asBinder());
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ typedFuture.complete(null);
+ return;
+ }
+ typedFuture.complete(mImms.createInputContentUriTokenLocked(
+ contentUri, packageName, mUserData).asBinder());
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6998,7 +6865,12 @@
@BinderThread
@Override
public void reportFullscreenModeAsync(boolean fullscreen) {
- mImms.reportFullscreenMode(mToken, fullscreen, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.reportFullscreenModeLocked(fullscreen, mUserData);
+ }
}
@BinderThread
@@ -7006,8 +6878,14 @@
public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethod(mToken, id, mUserData);
- typedFuture.complete(null);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ typedFuture.complete(null);
+ return;
+ }
+ mImms.setInputMethodAndSubtypeLocked(id, null /* subtype */, mUserData);
+ typedFuture.complete(null);
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7019,8 +6897,14 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserData);
- typedFuture.complete(null);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ typedFuture.complete(null);
+ return;
+ }
+ mImms.setInputMethodAndSubtypeLocked(id, subtype, mUserData);
+ typedFuture.complete(null);
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7033,8 +6917,25 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserData);
- typedFuture.complete(null);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+ typedFuture.complete(null);
+ return;
+ }
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mImms.hideMySoftInputLocked(statsToken, flags, reason, mUserData);
+ typedFuture.complete(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7047,8 +6948,25 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserData);
- typedFuture.complete(null);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+ typedFuture.complete(null);
+ return;
+ }
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mImms.showMySoftInputLocked(statsToken, flags, reason, mUserData);
+ typedFuture.complete(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7057,7 +6975,17 @@
@BinderThread
@Override
public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) {
- mImms.updateStatusIcon(mToken, packageName, iconId, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mImms.updateStatusIconLocked(packageName, iconId, mUserData);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
@BinderThread
@@ -7065,7 +6993,13 @@
public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserData));
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ typedFuture.complete(false);
+ return;
+ }
+ typedFuture.complete(mImms.switchToPreviousInputMethodLocked(mUserData));
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7077,8 +7011,14 @@
AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme,
- mUserData));
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ typedFuture.complete(false);
+ return;
+ }
+ typedFuture.complete(mImms.switchToNextInputMethodLocked(onlyCurrentIme,
+ mUserData));
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7089,8 +7029,14 @@
public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken,
- mUserData));
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ typedFuture.complete(false);
+ return;
+ }
+ typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethodLocked(
+ mUserData));
+ }
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -7099,39 +7045,68 @@
@BinderThread
@Override
public void onImeSwitchButtonClickFromClient(int displayId) {
- mImms.onImeSwitchButtonClickFromClient(mToken, displayId, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.onImeSwitchButtonClickLocked(displayId, mUserData);
+ }
}
@BinderThread
@Override
public void notifyUserActionAsync() {
- mImms.notifyUserAction(mToken, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.notifyUserActionLocked(mUserData);
+ }
}
@BinderThread
@Override
public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
@NonNull ImeTracker.Token statsToken) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+ return;
+ }
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
+ mImms.applyImeVisibilityLocked(windowToken, setVisible, statsToken, mUserData);
+ }
}
@BinderThread
@Override
public void onStylusHandwritingReady(int requestId, int pid) {
- mImms.onStylusHandwritingReady(requestId, pid, mUserData);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.onStylusHandwritingReadyLocked(requestId, pid, mUserData);
+ }
}
@BinderThread
@Override
public void resetStylusHandwriting(int requestId) {
- mImms.resetStylusHandwriting(requestId);
+ synchronized (ImfLock.class) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
+ return;
+ }
+ mImms.resetStylusHandwritingLocked(requestId);
+ }
}
@BinderThread
@Override
public void switchKeyboardLayoutAsync(int direction) {
synchronized (ImfLock.class) {
- if (!mImms.calledWithValidTokenLocked(mToken, mUserData)) {
+ if (!calledWithValidTokenLocked(mToken, mUserData)) {
return;
}
final long ident = Binder.clearCallingIdentity();
@@ -7142,5 +7117,25 @@
}
}
}
+
+ /**
+ * Returns true iff the caller is identified to be the current input method with the token.
+ *
+ * @param token the window token given to the input method when it was started
+ * @param userData {@link UserData} of the calling IME process
+ * @return true if and only if non-null valid token is specified
+ */
+ @GuardedBy("ImfLock.class")
+ private static boolean calledWithValidTokenLocked(@NonNull IBinder token,
+ @NonNull UserData userData) {
+ Objects.requireNonNull(token, "token must not be null");
+ final var bindingController = userData.mBindingController;
+ if (token != bindingController.getCurToken()) {
+ Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
+ + " uid:" + Binder.getCallingUid() + " token:" + token);
+ return false;
+ }
+ return true;
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index f16a5a0..b5ee068 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -17,7 +17,7 @@
package com.android.server.inputmethod;
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -62,7 +62,7 @@
private View mSwitchingDialogTitleView;
private List<ImeSubtypeListItem> mImList;
private InputMethodInfo[] mIms;
- private int[] mSubtypeIds;
+ private int[] mSubtypeIndices;
private boolean mShowImeWithHardKeyboard;
@@ -77,7 +77,7 @@
@GuardedBy("ImfLock.class")
void showInputMethodMenuLocked(boolean showAuxSubtypes, int displayId,
- String preferredInputMethodId, int preferredInputMethodSubtypeId,
+ String preferredInputMethodId, int preferredInputMethodSubtypeIndex,
@NonNull List<ImeSubtypeListItem> imList, @UserIdInt int userId) {
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
@@ -85,14 +85,14 @@
hideInputMethodMenuLocked(userId);
- if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
+ if (preferredInputMethodSubtypeIndex == NOT_A_SUBTYPE_INDEX) {
final InputMethodSubtype currentSubtype =
bindingController.getCurrentInputMethodSubtype();
if (currentSubtype != null) {
final String curMethodId = bindingController.getSelectedMethodId();
final InputMethodInfo currentImi =
InputMethodSettingsRepository.get(userId).getMethodMap().get(curMethodId);
- preferredInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+ preferredInputMethodSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(
currentImi, currentSubtype.hashCode());
}
}
@@ -101,7 +101,7 @@
final int size = imList.size();
mImList = imList;
mIms = new InputMethodInfo[size];
- mSubtypeIds = new int[size];
+ mSubtypeIndices = new int[size];
// No items are checked by default. When we have a list of explicitly enabled subtypes,
// the implicit subtype is no longer listed, but if it is still the selected one,
// no items will be shown as checked.
@@ -109,12 +109,13 @@
for (int i = 0; i < size; ++i) {
final ImeSubtypeListItem item = imList.get(i);
mIms[i] = item.mImi;
- mSubtypeIds[i] = item.mSubtypeId;
+ mSubtypeIndices[i] = item.mSubtypeIndex;
if (mIms[i].getId().equals(preferredInputMethodId)) {
- int subtypeId = mSubtypeIds[i];
- if ((subtypeId == NOT_A_SUBTYPE_ID)
- || (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
- || (subtypeId == preferredInputMethodSubtypeId)) {
+ int subtypeIndex = mSubtypeIndices[i];
+ if ((subtypeIndex == NOT_A_SUBTYPE_INDEX)
+ || (preferredInputMethodSubtypeIndex == NOT_A_SUBTYPE_INDEX
+ && subtypeIndex == 0)
+ || (subtypeIndex == preferredInputMethodSubtypeIndex)) {
checkedItem = i;
}
}
@@ -123,7 +124,7 @@
if (checkedItem == -1) {
Slog.w(TAG, "Switching menu shown with no item selected"
+ ", IME id: " + preferredInputMethodId
- + ", subtype index: " + preferredInputMethodSubtypeId);
+ + ", subtype index: " + preferredInputMethodSubtypeIndex);
}
if (mDialogWindowContext == null) {
@@ -171,19 +172,19 @@
com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
synchronized (ImfLock.class) {
- if (mIms == null || mIms.length <= which || mSubtypeIds == null
- || mSubtypeIds.length <= which) {
+ if (mIms == null || mIms.length <= which || mSubtypeIndices == null
+ || mSubtypeIndices.length <= which) {
return;
}
final InputMethodInfo im = mIms[which];
- int subtypeId = mSubtypeIds[which];
+ int subtypeIndex = mSubtypeIndices[which];
adapter.mCheckedItem = which;
adapter.notifyDataSetChanged();
if (im != null) {
- if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
- subtypeId = NOT_A_SUBTYPE_ID;
+ if (subtypeIndex < 0 || subtypeIndex >= im.getSubtypeCount()) {
+ subtypeIndex = NOT_A_SUBTYPE_INDEX;
}
- mService.setInputMethodLocked(im.getId(), subtypeId, userId);
+ mService.setInputMethodLocked(im.getId(), subtypeIndex, userId);
}
hideInputMethodMenuLocked(userId);
}
@@ -251,7 +252,7 @@
mDialogBuilder = null;
mImList = null;
mIms = null;
- mSubtypeIds = null;
+ mSubtypeIndices = null;
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index b72a34d..d9e9e00 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -21,7 +21,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
-import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -107,7 +107,7 @@
if (which != selectedIndex) {
final var item = items.get(which);
InputMethodManagerInternal.get()
- .switchToInputMethod(item.mImi.getId(), item.mSubtypeId, userId);
+ .switchToInputMethod(item.mImi.getId(), item.mSubtypeIndex, userId);
}
hide(displayId, userId);
};
@@ -225,10 +225,10 @@
/**
* The index of the subtype in the input method's array of subtypes,
- * or {@link InputMethodUtils#NOT_A_SUBTYPE_ID} if this item doesn't have a subtype.
+ * or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
*/
- @IntRange(from = NOT_A_SUBTYPE_ID)
- private final int mSubtypeId;
+ @IntRange(from = NOT_A_SUBTYPE_INDEX)
+ private final int mSubtypeIndex;
/** Whether this item has a group header (only the first item of each input method). */
private final boolean mHasHeader;
@@ -240,12 +240,13 @@
private final boolean mHasDivider;
MenuItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
- @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_ID) int subtypeId,
- boolean hasHeader, boolean hasDivider) {
+ @NonNull InputMethodInfo imi,
+ @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex, boolean hasHeader,
+ boolean hasDivider) {
mImeName = imeName;
mSubtypeName = subtypeName;
mImi = imi;
- mSubtypeId = subtypeId;
+ mSubtypeIndex = subtypeIndex;
mHasHeader = hasHeader;
mHasDivider = hasDivider;
}
@@ -255,7 +256,7 @@
return "MenuItem{"
+ "mImeName=" + mImeName
+ " mSubtypeName=" + mSubtypeName
- + " mSubtypeId=" + mSubtypeId
+ + " mSubtypeIndex=" + mSubtypeIndex
+ " mHasHeader=" + mHasHeader
+ " mHasDivider=" + mHasDivider
+ "}";
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 0152158..030a5fb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -54,7 +54,7 @@
/**
* An integer code that represents "no subtype" when a subtype hashcode is used.
*
- * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have
+ * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}, we have
* used {@code -1} here. We cannot change this value as it's already saved into secure settings.
* </p>
*/
@@ -62,7 +62,7 @@
/**
* A string code that represents "no subtype" when a subtype hashcode is used.
*
- * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_ID}, we have
+ * <p>Due to historical confusions with {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX}, we have
* used {@code "-1"} here. We cannot change this value as it's already saved into secure
* settings.</p>
*/
@@ -84,8 +84,8 @@
// Inputmethod and subtypes are saved in the settings as follows:
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
for (int i = 0; i < ime.second.size(); ++i) {
- final String subtypeId = ime.second.get(i);
- builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+ final String subtypeHashCode = ime.second.get(i);
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeHashCode);
}
}
@@ -350,12 +350,12 @@
if (lastImi == null) return null;
try {
final int lastSubtypeHash = Integer.parseInt(lastIme.second);
- final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+ final int lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi,
lastSubtypeHash);
- if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+ if (lastSubtypeIndex < 0 || lastSubtypeIndex >= lastImi.getSubtypeCount()) {
return null;
}
- return lastImi.getSubtypeAt(lastSubtypeId);
+ return lastImi.getSubtypeAt(lastSubtypeIndex);
} catch (NumberFormatException e) {
return null;
}
@@ -427,7 +427,7 @@
for (int j = 0; j < explicitlyEnabledSubtypes.size(); ++j) {
final String s = explicitlyEnabledSubtypes.get(j);
if (s.equals(subtypeHashCode)) {
- // If both imeId and subtype are enabled, return subtypeId.
+ // If both imeId and subtype are enabled, return subtypeHashCode.
try {
final int hashCode = Integer.parseInt(subtypeHashCode);
// Check whether the subtype is valid or not
@@ -494,11 +494,11 @@
putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
}
- void putSelectedSubtype(int subtypeId) {
+ void putSelectedSubtype(int subtypeHashCode) {
if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId);
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeHashCode + ", " + mUserId);
}
- putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+ putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeHashCode);
}
@Nullable
@@ -551,13 +551,13 @@
return mUserId;
}
- int getSelectedInputMethodSubtypeId(String selectedImiId) {
+ int getSelectedInputMethodSubtypeIndex(String selectedImiId) {
final InputMethodInfo imi = mMethodMap.get(selectedImiId);
if (imi == null) {
- return InputMethodUtils.NOT_A_SUBTYPE_ID;
+ return InputMethodUtils.NOT_A_SUBTYPE_INDEX;
}
final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+ return SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtypeHashCode);
}
void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 05cc598..4dbbfa2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,6 +16,8 @@
package com.android.server.inputmethod;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -48,7 +50,6 @@
final class InputMethodSubtypeSwitchingController {
private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
@IntDef(prefix = {"MODE_"}, value = {
MODE_STATIC,
@@ -86,17 +87,21 @@
public final CharSequence mSubtypeName;
@NonNull
public final InputMethodInfo mImi;
- public final int mSubtypeId;
+ /**
+ * The index of the subtype in the input method's array of subtypes,
+ * or {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if this item doesn't have a subtype.
+ */
+ public final int mSubtypeIndex;
public final boolean mIsSystemLocale;
public final boolean mIsSystemLanguage;
ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
- @NonNull InputMethodInfo imi, int subtypeId, @Nullable String subtypeLocale,
+ @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
@NonNull String systemLocale) {
mImeName = imeName;
mSubtypeName = subtypeName;
mImi = imi;
- mSubtypeId = subtypeId;
+ mSubtypeIndex = subtypeIndex;
if (TextUtils.isEmpty(subtypeLocale)) {
mIsSystemLocale = false;
mIsSystemLanguage = false;
@@ -137,7 +142,7 @@
* <li>{@link #mImi} with {@link InputMethodInfo#getId()}</li>
* </ol>
* Note: this class has a natural ordering that is inconsistent with
- * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeId} but
+ * {@link #equals(Object)}. This method doesn't compare {@link #mSubtypeIndex} but
* {@link #equals(Object)} does.
*
* @param other the object to be compared.
@@ -177,7 +182,7 @@
return "ImeSubtypeListItem{"
+ "mImeName=" + mImeName
+ " mSubtypeName=" + mSubtypeName
- + " mSubtypeId=" + mSubtypeId
+ + " mSubtypeIndex=" + mSubtypeIndex
+ " mIsSystemLocale=" + mIsSystemLocale
+ " mIsSystemLanguage=" + mIsSystemLanguage
+ "}";
@@ -190,7 +195,8 @@
}
if (o instanceof ImeSubtypeListItem) {
final ImeSubtypeListItem that = (ImeSubtypeListItem) o;
- return Objects.equals(this.mImi, that.mImi) && this.mSubtypeId == that.mSubtypeId;
+ return Objects.equals(this.mImi, that.mImi)
+ && this.mSubtypeIndex == that.mSubtypeIndex;
}
return false;
}
@@ -256,7 +262,7 @@
}
}
} else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
mSystemLocaleStr));
}
}
@@ -310,17 +316,17 @@
}
}
} else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
mSystemLocaleStr));
}
}
return imList;
}
- private static int calculateSubtypeId(@NonNull InputMethodInfo imi,
+ private static int calculateSubtypeIndex(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
- return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
- : NOT_A_SUBTYPE_ID;
+ return subtype != null ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode())
+ : NOT_A_SUBTYPE_INDEX;
}
private static class StaticRotationList {
@@ -341,12 +347,12 @@
* @return The index in the given list. -1 if not found.
*/
private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
- final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+ final int currentSubtypeIndex = calculateSubtypeIndex(imi, subtype);
final int numSubtypes = mImeSubtypeList.size();
for (int i = 0; i < numSubtypes; ++i) {
final ImeSubtypeListItem item = mImeSubtypeList.get(i);
// Skip until the current IME/subtype is found.
- if (imi.equals(item.mImi) && item.mSubtypeId == currentSubtypeId) {
+ if (imi.equals(item.mImi) && item.mSubtypeIndex == currentSubtypeIndex) {
return i;
}
}
@@ -414,14 +420,14 @@
*/
private int getUsageRank(@NonNull InputMethodInfo imi,
@Nullable InputMethodSubtype subtype) {
- final int currentSubtypeId = calculateSubtypeId(imi, subtype);
+ final int currentSubtypeIndex = calculateSubtypeIndex(imi, subtype);
final int numItems = mUsageHistoryOfSubtypeListItemIndex.length;
for (int usageRank = 0; usageRank < numItems; usageRank++) {
final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
final ImeSubtypeListItem subtypeListItem =
mImeSubtypeList.get(subtypeListItemIndex);
if (subtypeListItem.mImi.equals(imi)
- && subtypeListItem.mSubtypeId == currentSubtypeId) {
+ && subtypeListItem.mSubtypeIndex == currentSubtypeIndex) {
return usageRank;
}
}
@@ -575,11 +581,11 @@
@IntRange(from = -1)
private int getIndex(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype,
boolean useRecency) {
- final int subtypeIndex = calculateSubtypeId(imi, subtype);
+ final int subtypeIndex = calculateSubtypeIndex(imi, subtype);
for (int i = 0; i < mItems.size(); i++) {
final int mappedIndex = useRecency ? mRecencyMap[i] : i;
final var item = mItems.get(mappedIndex);
- if (item.mImi.equals(imi) && item.mSubtypeId == subtypeIndex) {
+ if (item.mImi.equals(imi) && item.mSubtypeIndex == subtypeIndex) {
return i;
}
}
@@ -591,13 +597,13 @@
pw.println(prefix + "Static order:");
for (int i = 0; i < mItems.size(); ++i) {
final var item = mItems.get(i);
- pw.println(prefix + "i=" + i + " item=" + item);
+ pw.println(prefix + " i=" + i + " item=" + item);
}
pw.println(prefix + "Recency order:");
for (int i = 0; i < mRecencyMap.length; ++i) {
final int index = mRecencyMap[i];
final var item = mItems.get(index);
- pw.println(prefix + "i=" + i + " item=" + item);
+ pw.println(prefix + " i=" + i + " item=" + item);
}
}
}
@@ -800,7 +806,7 @@
pw.println(prefix + "mHardwareRotationList:");
mHardwareRotationList.dump(pw, prefix + " ");
}
- pw.println("User action since last switch: " + mUserActionSinceSwitch);
+ pw.println(prefix + "User action since last switch: " + mUserActionSinceSwitch);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 361cdbb..da35fe7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -59,7 +59,7 @@
*/
final class InputMethodUtils {
public static final boolean DEBUG = false;
- static final int NOT_A_SUBTYPE_ID = -1;
+ static final int NOT_A_SUBTYPE_INDEX = -1;
private static final String TAG = "InputMethodUtils";
// The string for enabled input method is saved as follows:
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 1b4c0d6..f615b52 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -16,6 +16,9 @@
package com.android.server.inputmethod;
+import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
+
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,7 +51,6 @@
static final String SUBTYPE_MODE_ANY = null;
static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
- static final int NOT_A_SUBTYPE_ID = -1;
private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
"EnabledWhenDefaultIsNotAsciiCapable";
@@ -103,10 +105,19 @@
}
static boolean isValidSubtypeHashCode(InputMethodInfo imi, int subtypeHashCode) {
- return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
+ return getSubtypeIndexFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_INDEX;
}
- static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+ /**
+ * Returns the index to be specified to {@link InputMethodInfo#getSubtypeAt(int)}.
+ *
+ * @param imi {@link InputMethodInfo} to be queried about
+ * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be queried about
+ *
+ * @return The index to be specified to {@link InputMethodInfo#getSubtypeAt(int)}.
+ * {@link InputMethodUtils#NOT_A_SUBTYPE_INDEX} if not found
+ */
+ static int getSubtypeIndexFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
if (imi != null) {
final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) {
@@ -116,7 +127,7 @@
}
}
}
- return NOT_A_SUBTYPE_ID;
+ return NOT_A_SUBTYPE_INDEX;
}
private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
@@ -242,7 +253,7 @@
* most applicable subtype, it will return the first subtype
* matched with mode
*
- * @return the most applicable subtypeId
+ * @return the most applicable {@link InputMethodSubtype}
*/
static InputMethodSubtype findLastResortApplicableSubtype(
List<InputMethodSubtype> subtypes, String mode, @NonNull String locale,
@@ -310,15 +321,15 @@
@Nullable InputMethodSubtype currentSubtype) {
final int userId = settings.getUserId();
final int selectedSubtypeHashCode = SecureSettingsWrapper.getInt(
- Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID, userId);
- if (selectedSubtypeHashCode != NOT_A_SUBTYPE_ID && currentSubtype != null
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, INVALID_SUBTYPE_HASHCODE, userId);
+ if (selectedSubtypeHashCode != INVALID_SUBTYPE_HASHCODE && currentSubtype != null
&& isValidSubtypeHashCode(imi, currentSubtype.hashCode())) {
return currentSubtype;
}
- final int subtypeId = settings.getSelectedInputMethodSubtypeId(imi.getId());
- if (subtypeId != NOT_A_SUBTYPE_ID) {
- return imi.getSubtypeAt(subtypeId);
+ final int subtypeIndex = settings.getSelectedInputMethodSubtypeIndex(imi.getId());
+ if (subtypeIndex != NOT_A_SUBTYPE_INDEX) {
+ return imi.getSubtypeAt(subtypeIndex);
}
// If there are no selected subtypes, the framework will try to find the most applicable
diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java
index 28394c6a..96da17e 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -19,14 +19,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.util.Pair;
import android.util.SparseArray;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -149,11 +152,30 @@
String mLastEnabledInputMethodsStr = "";
/**
+ * A temporary solution to Bug 356879517, where we need to emulate the previous single-user mode
+ * behavior for KeyboardLayoutManager.
+ *
+ * <p>TODO(b/357663774): Remove this workaround</p>
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ Pair<InputMethodSubtypeHandle, InputMethodSubtype> mSubtypeForKeyboardLayoutMapping;
+
+ /**
* {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
*/
@NonNull
final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+ /**
+ * {@code true} if the user storage is considered to be unlocked.
+ *
+ * @see com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)
+ */
+ @NonNull
+ final AtomicBoolean mIsUnlockingOrUnlocked = new AtomicBoolean(false);
+
/**
* Intended to be instantiated only from this file.
*/
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
index f2714db..2bb3be6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -17,10 +17,12 @@
package com.android.server.location.contexthub;
import android.chre.flags.Flags;
+import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
-import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.Callable;
/**
* A class to manage behaviors during test mode. This is used for testing.
@@ -29,32 +31,31 @@
public class ContextHubTestModeManager {
private static final String TAG = "ContextHubTestModeManager";
- /** Probability of duplicating a message. */
- private static final double MESSAGE_DROP_PROBABILITY = 0.05;
-
- /** Probability of duplicating a message. */
- private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05;
+ private static final int DROP_MESSAGE_TO_HOST_EVENT = 0;
+ private static final int DROP_MESSAGE_TO_CONTEXT_HUB_EVENT = 1;
+ private static final int DUPLICATE_MESSAGE_TO_HOST_EVENT = 2;
+ private static final int DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT = 3;
+ private static final int NUMBER_OF_EVENTS = 4;
/** The number of total messages to send when the duplication event happens. */
private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
- /**
- * The seed for the random number generator. This is used to make the
- * test more deterministic.
- */
- private static final long SEED = 0xDEADBEEF;
-
- private final Random mRandom = new Random(SEED);
+ /** The counter to track the number of interactions with the test mode manager. */
+ private final AtomicLong mCounter = new AtomicLong(0);
/**
* @return whether the message was handled
* @see ContextHubServiceCallback#handleNanoappMessage
*/
public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
+ if (!message.isReliable()) {
+ return false;
+ }
+
+ long counterValue = mCounter.getAndIncrement();
if (Flags.reliableMessageDuplicateDetectionService()
- && message.isReliable()
- && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) {
- Log.i(TAG, "[TEST MODE] Duplicating message ("
+ && counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_HOST_EVENT) {
+ Log.i(TAG, "[TEST MODE] Duplicating message to host ("
+ NUM_MESSAGES_TO_DUPLICATE
+ " sends) with message sequence number: "
+ message.getMessageSequenceNumber());
@@ -63,6 +64,14 @@
}
return true;
}
+
+ if (counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_HOST_EVENT) {
+ Log.i(TAG, "[TEST MODE] Dropping message to host with "
+ + "message sequence number: "
+ + message.getMessageSequenceNumber());
+ return true;
+ }
+
return false;
}
@@ -70,14 +79,39 @@
* @return whether the message was handled
* @see IContextHubWrapper#sendMessageToContextHub
*/
- public boolean sendMessageToContextHub(NanoAppMessage message) {
+ public boolean sendMessageToContextHub(Callable<Integer> sendMessage, NanoAppMessage message) {
+ if (!message.isReliable()) {
+ return false;
+ }
+
+ long counterValue = mCounter.getAndIncrement();
+ if (counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT) {
+ Log.i(TAG, "[TEST MODE] Duplicating message to the Context Hub ("
+ + NUM_MESSAGES_TO_DUPLICATE
+ + " sends) with message sequence number: "
+ + message.getMessageSequenceNumber());
+ for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
+ try {
+ int result = sendMessage.call();
+ if (result != ContextHubTransaction.RESULT_SUCCESS) {
+ Log.e(TAG, "sendMessage returned an error: " + result);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in sendMessageToContextHub: "
+ + e.getMessage());
+ }
+ }
+ return true;
+ }
+
if (Flags.reliableMessageRetrySupportService()
- && message.isReliable()
- && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) {
- Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
+ && counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_CONTEXT_HUB_EVENT) {
+ Log.i(TAG, "[TEST MODE] Dropping message to the Context Hub with "
+ + "message sequence number: "
+ message.getMessageSequenceNumber());
return true;
}
+
return false;
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 4fc3d87..a8ad418 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -53,6 +53,7 @@
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.Callable;
/**
* @hide
@@ -659,32 +660,40 @@
@ContextHubTransaction.Result
public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
- NanoAppMessage message) throws RemoteException {
+ NanoAppMessage message) {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
- try {
- var msg = ContextHubServiceUtil.createAidlContextHubMessage(
- hostEndpointId, message);
-
- // Only process the message normally if not using test mode manager or if
- // the test mode manager call returned false as this indicates it did not
- // process the message.
- boolean useTestModeManager = Flags.reliableMessageImplementation()
- && Flags.reliableMessageTestModeBehavior()
- && mIsTestModeEnabled.get();
- if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) {
+ Callable<Integer> sendMessage = () -> {
+ try {
+ var msg = ContextHubServiceUtil.createAidlContextHubMessage(
+ hostEndpointId, message);
hub.sendMessageToHub(contextHubId, msg);
+ return ContextHubTransaction.RESULT_SUCCESS;
+ } catch (RemoteException | ServiceSpecificException e) {
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
+ };
- return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
- return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
- } catch (IllegalArgumentException e) {
- return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ // Only process the message normally if not using test mode manager or if
+ // the test mode manager call returned false as this indicates it did not
+ // process the message.
+ boolean useTestModeManager = Flags.reliableMessageImplementation()
+ && Flags.reliableMessageTestModeBehavior()
+ && mIsTestModeEnabled.get();
+ if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(
+ sendMessage, message)) {
+ try {
+ return sendMessage.call();
+ } catch (Exception e) {
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ }
}
+ return ContextHubTransaction.RESULT_SUCCESS;
}
@ContextHubTransaction.Result
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 53b6796..a6f4c0e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -530,6 +530,12 @@
*/
private boolean mUseDifferentDelaysForBackgroundChain;
+ /**
+ * Core uids and apps without the internet permission will not have any firewall rules applied
+ * to them.
+ */
+ private boolean mNeverApplyRulesToCoreUids;
+
// See main javadoc for instructions on how to use these locks.
final Object mUidRulesFirstLock = new Object();
final Object mNetworkPoliciesSecondLock = new Object();
@@ -622,16 +628,6 @@
@GuardedBy("mUidRulesFirstLock")
final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
- @GuardedBy("mUidRulesFirstLock")
- final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
- @GuardedBy("mUidRulesFirstLock")
- final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
- @GuardedBy("mUidRulesFirstLock")
- final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
- @GuardedBy("mUidRulesFirstLock")
- final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
- @GuardedBy("mUidRulesFirstLock")
- final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
/** Set of states for the child firewall chains. True if the chain is active. */
@GuardedBy("mUidRulesFirstLock")
@@ -770,7 +766,8 @@
/** List of apps indexed by uid and whether they have the internet permission */
@GuardedBy("mUidRulesFirstLock")
- private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
+ @VisibleForTesting
+ final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
/**
* Map of uid -> UidStateCallbackInfo objects holding the data received from
@@ -1048,6 +1045,7 @@
mUseMeteredFirewallChains = Flags.useMeteredFirewallChains();
mUseDifferentDelaysForBackgroundChain = Flags.useDifferentDelaysForBackgroundChain();
+ mNeverApplyRulesToCoreUids = Flags.neverApplyRulesToCoreUids();
synchronized (mUidRulesFirstLock) {
synchronized (mNetworkPoliciesSecondLock) {
@@ -4098,6 +4096,8 @@
+ mUseMeteredFirewallChains);
fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": "
+ mUseDifferentDelaysForBackgroundChain);
+ fout.println(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS + ": "
+ + mNeverApplyRulesToCoreUids);
fout.println();
fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
@@ -4589,7 +4589,7 @@
@VisibleForTesting
@GuardedBy("mUidRulesFirstLock")
void updateRestrictedModeAllowlistUL() {
- mUidFirewallRestrictedModeRules.clear();
+ final SparseIntArray uidRules = new SparseIntArray();
forEachUid("updateRestrictedModeAllowlist", uid -> {
synchronized (mUidRulesFirstLock) {
final int effectiveBlockedReasons = updateBlockedReasonsForRestrictedModeUL(
@@ -4599,13 +4599,13 @@
// setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
// non-default rules.
if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
- mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+ uidRules.append(uid, newFirewallRule);
}
}
});
if (mRestrictedNetworkingMode) {
// firewall rules only need to be set when this mode is being enabled.
- setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, mUidFirewallRestrictedModeRules);
+ setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, uidRules);
}
enableFirewallChainUL(FIREWALL_CHAIN_RESTRICTED, mRestrictedNetworkingMode);
}
@@ -4689,8 +4689,7 @@
void updateRulesForPowerSaveUL() {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForPowerSaveUL");
try {
- updateRulesForAllowlistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE,
- mUidFirewallPowerSaveRules);
+ updateRulesForAllowlistedPowerSaveUL(mRestrictPower, FIREWALL_CHAIN_POWERSAVE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
@@ -4705,8 +4704,7 @@
void updateRulesForDeviceIdleUL() {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForDeviceIdleUL");
try {
- updateRulesForAllowlistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE,
- mUidFirewallDozableRules);
+ updateRulesForAllowlistedPowerSaveUL(mDeviceIdleMode, FIREWALL_CHAIN_DOZABLE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
@@ -4720,13 +4718,11 @@
// NOTE: since both fw_dozable and fw_powersave uses the same map
// (mPowerSaveTempWhitelistAppIds) for allowlisting, we can reuse their logic in this method.
@GuardedBy("mUidRulesFirstLock")
- private void updateRulesForAllowlistedPowerSaveUL(boolean enabled, int chain,
- SparseIntArray rules) {
+ private void updateRulesForAllowlistedPowerSaveUL(boolean enabled, int chain) {
if (enabled) {
// Sync the allowlists before enabling the chain. We don't care about the rules if
// we are disabling the chain.
- final SparseIntArray uidRules = rules;
- uidRules.clear();
+ final SparseIntArray uidRules = new SparseIntArray();
final List<UserInfo> users = mUserManager.getUsers();
for (int ui = users.size() - 1; ui >= 0; ui--) {
UserInfo user = users.get(ui);
@@ -4755,9 +4751,7 @@
private void updateRulesForBackgroundChainUL() {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
try {
- final SparseIntArray uidRules = mUidFirewallBackgroundRules;
- uidRules.clear();
-
+ final SparseIntArray uidRules = new SparseIntArray();
final List<UserInfo> users = mUserManager.getUsers();
for (int ui = users.size() - 1; ui >= 0; ui--) {
final UserInfo user = users.get(ui);
@@ -4794,17 +4788,17 @@
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForLowPowerStandbyUL");
try {
if (mLowPowerStandbyActive) {
- mUidFirewallLowPowerStandbyModeRules.clear();
+ final SparseIntArray uidRules = new SparseIntArray();
for (int i = mUidState.size() - 1; i >= 0; i--) {
final int uid = mUidState.keyAt(i);
final int effectiveBlockedReasons = getEffectiveBlockedReasons(uid);
if (hasInternetPermissionUL(uid) && (effectiveBlockedReasons
& BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
- mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
+ uidRules.put(uid, FIREWALL_RULE_ALLOW);
}
}
setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY,
- mUidFirewallLowPowerStandbyModeRules, CHAIN_TOGGLE_ENABLE);
+ uidRules, CHAIN_TOGGLE_ENABLE);
} else {
setUidFirewallRulesUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, null, CHAIN_TOGGLE_DISABLE);
}
@@ -4822,10 +4816,8 @@
final int effectiveBlockedReasons = getEffectiveBlockedReasons(uid);
if (mUidState.contains(uid)
&& (effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) {
- mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW);
setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW);
} else {
- mUidFirewallLowPowerStandbyModeRules.delete(uid);
setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT);
}
}
@@ -4896,6 +4888,12 @@
int[] idleUids = mUsageStats.getIdleUidsForUser(user.id);
for (int uid : idleUids) {
if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) {
+ if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+ // This check is needed to keep mUidFirewallStandbyRules free of any
+ // such uids. Doing this keeps it in sync with the actual rules applied
+ // in the underlying connectivity stack.
+ continue;
+ }
// quick check: if this uid doesn't have INTERNET permission, it
// doesn't have network access anyway, so it is a waste to mess
// with it here.
@@ -5198,6 +5196,11 @@
@GuardedBy("mUidRulesFirstLock")
private boolean isUidValidForAllowlistRulesUL(int uid) {
+ return isUidValidForRulesUL(uid);
+ }
+
+ @GuardedBy("mUidRulesFirstLock")
+ private boolean isUidValidForRulesUL(int uid) {
return UserHandle.isApp(uid) && hasInternetPermissionUL(uid);
}
@@ -5313,16 +5316,11 @@
mActivityManagerInternal.onUidBlockedReasonsChanged(uid, BLOCKED_REASON_NONE);
mUidPolicy.delete(uid);
mUidFirewallStandbyRules.delete(uid);
- mUidFirewallDozableRules.delete(uid);
- mUidFirewallPowerSaveRules.delete(uid);
- mUidFirewallBackgroundRules.delete(uid);
mBackgroundTransitioningUids.delete(uid);
mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
mPowerSaveWhitelistAppIds.delete(uid);
mPowerSaveTempWhitelistAppIds.delete(uid);
mAppIdleTempWhitelistAppIds.delete(uid);
- mUidFirewallRestrictedModeRules.delete(uid);
- mUidFirewallLowPowerStandbyModeRules.delete(uid);
synchronized (mUidStateCallbackInfos) {
mUidStateCallbackInfos.remove(uid);
}
@@ -6217,41 +6215,33 @@
}
}
- private void addSdkSandboxUidsIfNeeded(SparseIntArray uidRules) {
- final int size = uidRules.size();
- final SparseIntArray sdkSandboxUids = new SparseIntArray();
- for (int index = 0; index < size; index++) {
- final int uid = uidRules.keyAt(index);
- final int rule = uidRules.valueAt(index);
- if (Process.isApplicationUid(uid)) {
- sdkSandboxUids.put(Process.toSdkSandboxUid(uid), rule);
- }
- }
-
- for (int index = 0; index < sdkSandboxUids.size(); index++) {
- final int uid = sdkSandboxUids.keyAt(index);
- final int rule = sdkSandboxUids.valueAt(index);
- uidRules.put(uid, rule);
- }
- }
-
/**
* Set uid rules on a particular firewall chain. This is going to synchronize the rules given
* here to netd. It will clean up dead rules and make sure the target chain only contains rules
* specified here.
*/
+ @GuardedBy("mUidRulesFirstLock")
private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
- addSdkSandboxUidsIfNeeded(uidRules);
try {
int size = uidRules.size();
- int[] uids = new int[size];
- int[] rules = new int[size];
+ final IntArray uids = new IntArray(size);
+ final IntArray rules = new IntArray(size);
for(int index = size - 1; index >= 0; --index) {
- uids[index] = uidRules.keyAt(index);
- rules[index] = uidRules.valueAt(index);
+ final int uid = uidRules.keyAt(index);
+ if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+ continue;
+ }
+ uids.add(uid);
+ rules.add(uidRules.valueAt(index));
+ if (Process.isApplicationUid(uid)) {
+ uids.add(Process.toSdkSandboxUid(uid));
+ rules.add(uidRules.valueAt(index));
+ }
}
- mNetworkManager.setFirewallUidRules(chain, uids, rules);
- mLogger.firewallRulesChanged(chain, uids, rules);
+ final int[] uidArray = uids.toArray();
+ final int[] ruleArray = rules.toArray();
+ mNetworkManager.setFirewallUidRules(chain, uidArray, ruleArray);
+ mLogger.firewallRulesChanged(chain, uidArray, ruleArray);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -6264,26 +6254,17 @@
*/
@GuardedBy("mUidRulesFirstLock")
private void setUidFirewallRuleUL(int chain, int uid, int rule) {
+ if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) {
+ return;
+ }
if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
"setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule);
}
try {
- if (chain == FIREWALL_CHAIN_DOZABLE) {
- mUidFirewallDozableRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_STANDBY) {
+ if (chain == FIREWALL_CHAIN_STANDBY) {
mUidFirewallStandbyRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
- mUidFirewallPowerSaveRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_RESTRICTED) {
- mUidFirewallRestrictedModeRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
- mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
- mUidFirewallBackgroundRules.put(uid, rule);
}
- // Note that we do not need keep a separate cache of uid rules for chains that we do
- // not call #setUidFirewallRulesUL for.
try {
mNetworkManager.setFirewallUidRule(chain, uid, rule);
@@ -6328,6 +6309,8 @@
* Resets all firewall rules associated with an UID.
*/
private void resetUidFirewallRules(int uid) {
+ // Resetting rules for uids with isUidValidForRulesUL = false should be OK as no rules
+ // should be previously set and the downstream code will skip no-op changes.
try {
mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_DOZABLE, uid,
FIREWALL_RULE_DEFAULT);
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 586baf0..7f04e66 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -27,3 +27,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "never_apply_rules_to_core_uids"
+ namespace: "backstage_power"
+ description: "Removes all rule bookkeeping and evaluation logic for core uids and uids without the internet permission"
+ bug: "356956588"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3cc0457..a44e553 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -17,7 +17,7 @@
package com.android.server.notification;
import static android.service.notification.Condition.STATE_TRUE;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import android.app.INotificationManager;
import android.app.NotificationManager;
@@ -326,7 +326,7 @@
// if user turned on the mode, ignore the update unless the app also wants the
// mode on. this will update the origin of the mode and let the owner turn it
// off when the context ends
- if (r.condition != null && r.condition.source == UPDATE_ORIGIN_USER) {
+ if (r.condition != null && r.condition.source == ORIGIN_USER_IN_SYSTEMUI) {
if (r.condition.state == STATE_TRUE && c.state == STATE_TRUE) {
r.condition = c;
}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index d060f8f2..c8cb54f 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -31,7 +31,7 @@
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -72,7 +72,7 @@
}
@Override
- public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) {
+ public void apply(ZenDeviceEffects effects, @ConfigOrigin int origin) {
Binder.withCleanCallingIdentity(() -> {
if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
!= effects.shouldSuppressAmbientDisplay()) {
@@ -120,15 +120,16 @@
mLastAppliedEffects = effects;
}
- private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) {
+ private void updateOrScheduleNightMode(boolean useNightMode, @ConfigOrigin int origin) {
mPendingNightMode = useNightMode;
// Changing the theme can be disruptive for the user (Activities are likely recreated, may
// lose some state). Therefore we only apply the change immediately if the rule was
// activated manually, or we are initializing, or the screen is currently off/dreaming.
- if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
- || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
- || origin == ZenModeConfig.UPDATE_ORIGIN_USER
+ if (origin == ZenModeConfig.ORIGIN_INIT
+ || origin == ZenModeConfig.ORIGIN_INIT_USER
+ || origin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
+ || origin == ZenModeConfig.ORIGIN_USER_IN_APP
|| !mPowerManager.isInteractive()) {
unregisterScreenOffReceiver();
updateNightModeImmediately(useNightMode);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index bd551fb..b4459cb 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -1194,9 +1194,9 @@
}
boolean shouldIgnoreNotification(final NotificationRecord record) {
- // Ignore group summaries
- return (record.getSbn().isGroup() && record.getSbn().getNotification()
- .isGroupSummary());
+ // Ignore auto-group summaries => don't count them as app-posted notifications
+ // for the cooldown budget
+ return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4179edd..c7c984b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2963,8 +2963,9 @@
};
cancelGroupChildrenLocked(userId, pkg, Binder.getCallingUid(),
Binder.getCallingPid(), null,
- false, childrenFlagChecker, groupKey,
- REASON_APP_CANCEL, SystemClock.elapsedRealtime());
+ false, childrenFlagChecker,
+ NotificationManagerService::wasChildOfForceRegroupedGroupChecker,
+ groupKey, REASON_APP_CANCEL, SystemClock.elapsedRealtime());
}
}
});
@@ -5771,16 +5772,20 @@
Binder.getCallingUid());
}
- @ZenModeConfig.ConfigChangeOrigin
+ @ZenModeConfig.ConfigOrigin
private int computeZenOrigin(boolean fromUser) {
// "fromUser" is introduced with MODES_API, so only consider it in that case.
- // (Non-MODES_API behavior should also not depend at all on UPDATE_ORIGIN_USER).
+ // (Non-MODES_API behavior should also not depend at all on ORIGIN_USER_IN_X).
if (android.app.Flags.modesApi() && fromUser) {
- return ZenModeConfig.UPDATE_ORIGIN_USER;
+ if (isCallerSystemOrSystemUi()) {
+ return ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
+ } else {
+ return ZenModeConfig.ORIGIN_USER_IN_APP;
+ }
} else if (isCallerSystemOrSystemUi()) {
- return ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+ return ZenModeConfig.ORIGIN_SYSTEM;
} else {
- return ZenModeConfig.UPDATE_ORIGIN_APP;
+ return ZenModeConfig.ORIGIN_APP;
}
}
@@ -6137,7 +6142,7 @@
enforcePolicyAccess(pkg, "setNotificationPolicy");
enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
- @ZenModeConfig.ConfigChangeOrigin int origin = computeZenOrigin(fromUser);
+ @ZenModeConfig.ConfigOrigin int origin = computeZenOrigin(fromUser);
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -8663,8 +8668,8 @@
if (r.getNotification().isGroupSummary()) {
cancelGroupChildrenLocked(mUserId, mPkg, mCallingUid, mCallingPid,
listenerName, mSendDelete, childrenFlagChecker,
- r.getNotification().getGroup(), mReason,
- mCancellationElapsedTimeMs);
+ NotificationManagerService::isChildOfCurrentGroupChecker,
+ r.getGroupKey(), mReason, mCancellationElapsedTimeMs);
}
mAttentionHelper.updateLightsLocked();
if (mShortcutHelper != null) {
@@ -9386,8 +9391,8 @@
if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
cancelGroupChildrenLocked(old.getUserId(), old.getSbn().getPackageName(), callingUid,
callingPid, null, false /* sendDelete */, childrenFlagChecker,
- old.getNotification().getGroup(), REASON_APP_CANCEL,
- SystemClock.elapsedRealtime());
+ NotificationManagerService::isChildOfCurrentGroupChecker, old.getGroupKey(),
+ REASON_APP_CANCEL, SystemClock.elapsedRealtime());
}
}
@@ -10368,13 +10373,45 @@
public boolean apply(int flags);
}
- private static boolean isChildOfGroup(final NotificationRecord childRecord, int userId,
+ @FunctionalInterface
+ private interface GroupChildChecker {
+ // Returns true if the childRecord is a child of the group defined
+ // by the rest of the parameters
+ boolean apply(NotificationRecord childRecord, int userId, String pkg, String groupKey);
+ }
+
+ /**
+ * Checks that the notification is currently a child of the group
+ * @param childRecord the notification to check
+ * @param userId userId of the group
+ * @param pkg package name of the group
+ * @param groupKey group key for a current group
+ * @return true if the childRecord is currently a child of the group
+ */
+ private static boolean isChildOfCurrentGroupChecker(NotificationRecord childRecord, int userId,
String pkg, String groupKey) {
return (childRecord.getUser().getIdentifier() == userId
&& childRecord.getSbn().getPackageName().equals(pkg)
&& childRecord.getSbn().isGroup()
&& !childRecord.getNotification().isGroupSummary()
- && TextUtils.equals(groupKey, childRecord.getNotification().getGroup()));
+ && TextUtils.equals(groupKey, childRecord.getGroupKey()));
+ }
+
+ /**
+ * Checks that the notification was originally a child of the group
+ * @param childRecord the notification to check
+ * @param userId userId of the group
+ * @param pkg package name of the group
+ * @param groupKey original/initial group key for a group that was force grouped
+ * @return true if the childRecord was originally a child of the group
+ */
+ private static boolean wasChildOfForceRegroupedGroupChecker(NotificationRecord childRecord,
+ int userId, String pkg, String groupKey) {
+ return (childRecord.getUser().getIdentifier() == userId
+ && childRecord.getSbn().getPackageName().equals(pkg)
+ && childRecord.getSbn().isGroup()
+ && !childRecord.getNotification().isGroupSummary()
+ && TextUtils.equals(groupKey, childRecord.getOriginalGroupKey()));
}
@GuardedBy("mNotificationLock")
@@ -10535,18 +10572,19 @@
// Warning: The caller is responsible for invoking updateLightsLocked().
@GuardedBy("mNotificationLock")
private void cancelGroupChildrenLocked(int userId, String pkg, int callingUid, int callingPid,
- String listenerName, boolean sendDelete, FlagChecker flagChecker, String groupKey,
- int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
+ String listenerName, boolean sendDelete, FlagChecker flagChecker,
+ GroupChildChecker groupChildChecker, String groupKey, int reason,
+ @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
if (pkg == null) {
if (DBG) Slog.e(TAG, "No package for group summary");
return;
}
cancelGroupChildrenByListLocked(mNotificationList, userId, pkg, callingUid, callingPid,
- listenerName, sendDelete, true, flagChecker, groupKey,
+ listenerName, sendDelete, true, flagChecker, groupChildChecker, groupKey,
reason, cancellationElapsedTimeMs);
cancelGroupChildrenByListLocked(mEnqueuedNotifications, userId, pkg, callingUid, callingPid,
- listenerName, sendDelete, false, flagChecker, groupKey,
+ listenerName, sendDelete, false, flagChecker, groupChildChecker, groupKey,
reason, cancellationElapsedTimeMs);
}
@@ -10554,12 +10592,13 @@
private void cancelGroupChildrenByListLocked(ArrayList<NotificationRecord> notificationList,
int userId, String pkg, int callingUid, int callingPid,
String listenerName, boolean sendDelete, boolean wasPosted, FlagChecker flagChecker,
- String groupKey, int reason, @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
+ GroupChildChecker grouChildChecker, String groupKey, int reason,
+ @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
final int childReason = REASON_GROUP_SUMMARY_CANCELED;
for (int i = notificationList.size() - 1; i >= 0; i--) {
final NotificationRecord childR = notificationList.get(i);
final StatusBarNotification childSbn = childR.getSbn();
- if (isChildOfGroup(childR, userId, pkg, groupKey)
+ if (grouChildChecker.apply(childR, userId, pkg, groupKey)
&& (flagChecker == null || flagChecker.apply(childR.getFlags()))
&& (!childR.getChannel().isImportantConversation() || reason != REASON_CANCEL)) {
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index bd00901..1392003 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1163,6 +1163,21 @@
getSbn().setOverrideGroupKey(overrideGroupKey);
}
+ /**
+ * Get the original group key that was set via {@link Notification.Builder#setGroup}
+ *
+ * This value is different than the value returned by {@link #getGroupKey()} as it does
+ * not contain any userId or package name.
+ *
+ * This value is different than the value returned
+ * by {@link StatusBarNotification#getGroup()} if the notification group
+ * was overridden: by NotificationAssistantService or by autogrouping.
+ */
+ @Nullable
+ public String getOriginalGroupKey() {
+ return getSbn().getNotification().getGroup();
+ }
+
public NotificationChannel getChannel() {
return mChannel;
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index c09504f..821722b 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -30,7 +30,6 @@
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Flags.notificationClassification;
@@ -1979,8 +1978,8 @@
(areChannelsBypassingDnd
? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
policy.priorityConversationSenders),
- fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ fromSystemOrSystemUi ? ZenModeConfig.ORIGIN_SYSTEM
+ : ZenModeConfig.ORIGIN_APP,
callingUid);
}
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 3650536..268d835 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -116,8 +116,8 @@
if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
final int callingUid = Binder.getCallingUid();
mHelper.setConfig(mHelper.getConfig(), component,
- callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
+ : ZenModeConfig.ORIGIN_APP,
"zmc.onServiceAdded:" + component, callingUid);
}
@@ -128,8 +128,8 @@
if (config == null) return;
final int callingUid = Binder.getCallingUid();
mHelper.setAutomaticZenRuleState(id, condition,
- callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
+ : ZenModeConfig.ORIGIN_APP,
callingUid);
}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 4a82057..b03a54e 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -34,7 +34,7 @@
import android.service.notification.DNDPolicyProto;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
@@ -113,7 +113,7 @@
* @param origin The origin of the Zen change.
*/
public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
- @ConfigChangeOrigin int origin) {
+ @ConfigOrigin int origin) {
mChangeState.init(prevInfo, newInfo, callingUid, origin);
if (mChangeState.shouldLogChanges()) {
maybeReassignCallingUid();
@@ -250,11 +250,11 @@
ZenModeConfig mPrevConfig, mNewConfig;
NotificationManager.Policy mPrevPolicy, mNewPolicy;
int mCallingUid = Process.INVALID_UID;
- @ConfigChangeOrigin
- int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+ @ConfigOrigin
+ int mOrigin = ZenModeConfig.ORIGIN_UNKNOWN;
private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
- @ConfigChangeOrigin int origin) {
+ @ConfigOrigin int origin) {
// previous & new may be the same -- that would indicate that zen mode hasn't changed.
mPrevZenMode = prevInfo.mZenMode;
mNewZenMode = newInfo.mZenMode;
@@ -484,7 +484,8 @@
*/
boolean getIsUserAction() {
if (Flags.modesApi()) {
- return mOrigin == ZenModeConfig.UPDATE_ORIGIN_USER;
+ return mOrigin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
+ || mOrigin == ZenModeConfig.ORIGIN_USER_IN_APP;
}
// Approach for pre-MODES_API:
@@ -549,10 +550,10 @@
}
boolean isFromSystemOrSystemUi() {
- return mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT
- || mOrigin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
- || mOrigin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- || mOrigin == ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
+ return mOrigin == ZenModeConfig.ORIGIN_INIT
+ || mOrigin == ZenModeConfig.ORIGIN_INIT_USER
+ || mOrigin == ZenModeConfig.ORIGIN_SYSTEM
+ || mOrigin == ZenModeConfig.ORIGIN_RESTORE_BACKUP;
}
/**
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 95d8bb9..8c280ed 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -29,13 +29,14 @@
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_RESTORE_BACKUP;
+import static android.service.notification.ZenModeConfig.ORIGIN_SYSTEM;
+import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -95,7 +96,7 @@
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
+import android.service.notification.ZenModeConfig.ConfigOrigin;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeProto;
import android.service.notification.ZenPolicy;
@@ -294,7 +295,7 @@
// "update" config to itself, which will have no effect in the case where a config
// was read in via XML, but will initialize zen mode if nothing was read in and the
// config remains the default.
- updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init",
+ updateConfigAndZenModeLocked(mConfig, ORIGIN_INIT, "init",
true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */);
}
}
@@ -328,7 +329,7 @@
}
mDeviceEffectsApplier = deviceEffectsApplier;
}
- applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT);
+ applyConsolidatedDeviceEffects(ORIGIN_INIT);
}
public void onUserSwitched(int user) {
@@ -368,7 +369,7 @@
config.user = user;
}
synchronized (mConfigLock) {
- setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason,
+ setConfigLocked(config, null, ORIGIN_INIT_USER, reason,
Process.SYSTEM_UID);
}
cleanUpZenRules();
@@ -384,7 +385,7 @@
final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (newZen != -1) {
setManualZenMode(newZen, null,
- fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP,
+ fromSystemOrSystemUi ? ORIGIN_SYSTEM : ORIGIN_APP,
/* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null),
/* caller= */ name != null ? name.getPackageName() : null,
callingUid);
@@ -447,8 +448,8 @@
}
public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
- @ConfigChangeOrigin int origin, String reason, int callingUid) {
- requirePublicOrigin("addAutomaticZenRule", origin);
+ @ConfigOrigin int origin, String reason, int callingUid) {
+ checkManageRuleOrigin("addAutomaticZenRule", origin);
if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
if (component == null) {
@@ -497,7 +498,7 @@
@GuardedBy("mConfigLock")
private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
- AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) {
+ AutomaticZenRule azrToAdd, @ConfigOrigin int origin) {
if (!Flags.modesApi()) {
return ruleToAdd;
}
@@ -517,7 +518,7 @@
config.deletedRules.remove(deletedKey);
ruleToRestore.deletionInstant = null;
- if (origin != UPDATE_ORIGIN_APP) {
+ if (origin != ORIGIN_APP) {
return ruleToAdd; // Okay to create anew.
}
@@ -547,8 +548,8 @@
}
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
- @ConfigChangeOrigin int origin, String reason, int callingUid) {
- requirePublicOrigin("updateAutomaticZenRule", origin);
+ @ConfigOrigin int origin, String reason, int callingUid) {
+ checkManageRuleOrigin("updateAutomaticZenRule", origin);
if (ruleId == null) {
throw new IllegalArgumentException("ruleId cannot be null");
}
@@ -621,7 +622,7 @@
mContext.getString(R.string.zen_mode_implicit_deactivated),
STATE_FALSE);
setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
- deactivated, UPDATE_ORIGIN_APP, callingUid);
+ deactivated, ORIGIN_APP, callingUid);
}
} else {
// Either create a new rule with a default ZenPolicy, or update an existing rule's
@@ -647,7 +648,7 @@
mContext.getString(R.string.zen_mode_implicit_activated),
STATE_TRUE);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, ORIGIN_APP,
"applyGlobalZenModeAsImplicitZenRule", callingUid);
}
}
@@ -701,7 +702,7 @@
/* updateBitmask= */ false,
isNew);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, ORIGIN_APP,
"applyGlobalPolicyAsImplicitZenRule", callingUid);
}
}
@@ -788,9 +789,9 @@
return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
}
- boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
+ boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
int callingUid) {
- requirePublicOrigin("removeAutomaticZenRule", origin);
+ checkManageRuleOrigin("removeAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -821,9 +822,9 @@
}
}
- boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
+ boolean removeAutomaticZenRules(String packageName, @ConfigOrigin int origin,
String reason, int callingUid) {
- requirePublicOrigin("removeAutomaticZenRules", origin);
+ checkManageRuleOrigin("removeAutomaticZenRules", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -837,7 +838,7 @@
}
// If the system is clearing all rules this means DND access is revoked or the package
// was uninstalled, so also clear the preserved-deleted rules.
- if (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI) {
+ if (origin == ORIGIN_SYSTEM) {
for (int i = newConfig.deletedRules.size() - 1; i >= 0; i--) {
ZenRule rule = newConfig.deletedRules.get(newConfig.deletedRules.keyAt(i));
if (Objects.equals(rule.getPkg(), packageName)) {
@@ -850,7 +851,7 @@
}
private void maybePreserveRemovedRule(ZenModeConfig config, ZenRule ruleToRemove,
- @ConfigChangeOrigin int origin) {
+ @ConfigOrigin int origin) {
if (!Flags.modesApi()) {
return;
}
@@ -859,7 +860,7 @@
// We don't try to preserve system-owned rules because their conditionIds (used as
// deletedRuleKey) are not stable. This is almost moot anyway because an app cannot
// delete a system-owned rule.
- if (origin == UPDATE_ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
+ if (origin == ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp()
&& !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) {
String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove);
if (deletedKey != null) {
@@ -888,9 +889,9 @@
}
}
- void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
+ void setAutomaticZenRuleState(String id, Condition condition, @ConfigOrigin int origin,
int callingUid) {
- requirePublicOrigin("setAutomaticZenRuleState", origin);
+ checkSetRuleStateOrigin("setAutomaticZenRuleState(String id)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -911,8 +912,8 @@
}
void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
- @ConfigChangeOrigin int origin, int callingUid) {
- requirePublicOrigin("setAutomaticZenRuleState", origin);
+ @ConfigOrigin int origin, int callingUid) {
+ checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -932,11 +933,13 @@
@GuardedBy("mConfigLock")
private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
- Condition condition, @ConfigChangeOrigin int origin, int callingUid) {
+ Condition condition, @ConfigOrigin int origin, int callingUid) {
if (rules == null || rules.isEmpty()) return;
- if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) {
- origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action.
+ if (!Flags.modesUi()) {
+ if (Flags.modesApi() && condition.source == SOURCE_USER_ACTION) {
+ origin = ORIGIN_USER_IN_APP; // Although coming from app, it's actually from user.
+ }
}
for (ZenRule rule : rules) {
@@ -1062,7 +1065,7 @@
}
}
if (updated) {
- setConfigLocked(config, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ setConfigLocked(config, null, ORIGIN_SYSTEM,
"updateZenRulesOnLocaleChange", Process.SYSTEM_UID);
}
}
@@ -1119,11 +1122,11 @@
* </ul>
*
* <p>The rule's {@link ZenRule#condition} is cleared (meaning that an active rule will be
- * deactivated) unless the update has origin == {@link ZenModeConfig#UPDATE_ORIGIN_USER}.
+ * deactivated) unless the update has origin == {@link ZenModeConfig#ORIGIN_USER_IN_SYSTEMUI}.
*/
@GuardedBy("mConfigLock")
private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
- @ConfigChangeOrigin int origin, boolean isNew) {
+ @ConfigOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
boolean modified = false;
// These values can always be edited by the app, so we apply changes immediately.
@@ -1137,7 +1140,7 @@
// Allow updating the CPS backing system rules (e.g. for custom manual -> schedule)
if (Flags.modesUi()
- && (origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI || origin == UPDATE_ORIGIN_USER)
+ && (origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI)
&& Objects.equals(rule.pkg, SystemZenRules.PACKAGE_ANDROID)
&& !Objects.equals(rule.component, azr.getOwner())) {
rule.component = azr.getOwner();
@@ -1151,7 +1154,7 @@
rule.disabledOrigin = origin;
} else if (azr.isEnabled()) {
// Enabling or previously enabled. Clear disabler.
- rule.disabledOrigin = UPDATE_ORIGIN_UNKNOWN;
+ rule.disabledOrigin = ORIGIN_UNKNOWN;
}
}
@@ -1166,7 +1169,7 @@
Flags.modesApi()
&& (Flags.modesUi() || isWatch)
&& !isNew
- && origin == UPDATE_ORIGIN_USER
+ && origin == ORIGIN_USER_IN_SYSTEMUI
&& rule.enabled == azr.isEnabled()
&& rule.conditionId != null
&& rule.condition != null
@@ -1232,7 +1235,7 @@
}
// Updates the bitmasks if the origin of the change is the user.
- boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
+ boolean updateBitmask = (origin == ORIGIN_USER_IN_SYSTEMUI);
if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
@@ -1263,7 +1266,7 @@
// Updates the bitmask and values for all device effect fields, based on the origin.
modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
- origin == UPDATE_ORIGIN_APP, updateBitmask);
+ origin == ORIGIN_APP, updateBitmask);
return modified;
} else {
@@ -1297,8 +1300,8 @@
* change. (Note that regardless of origin, fields can always be updated if they're not already
* user modified.)
*/
- private static boolean doesOriginAlwaysUpdateValues(@ConfigChangeOrigin int origin) {
- return origin == UPDATE_ORIGIN_USER || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+ private static boolean doesOriginAlwaysUpdateValues(@ConfigOrigin int origin) {
+ return origin == ORIGIN_USER_IN_SYSTEMUI || origin == ORIGIN_SYSTEM;
}
/**
@@ -1539,7 +1542,7 @@
: AUTOMATIC_RULE_STATUS_DISABLED);
}
- void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+ void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
String reason, @Nullable String caller, int callingUid) {
setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/,
callingUid);
@@ -1547,7 +1550,7 @@
Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0);
}
- private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin,
+ private void setManualZenMode(int zenMode, Uri conditionId, @ConfigOrigin int origin,
String reason, @Nullable String caller, boolean setRingerMode, int callingUid) {
ZenModeConfig newConfig;
synchronized (mConfigLock) {
@@ -1564,8 +1567,8 @@
newConfig.manualRule.zenMode = zenMode;
newConfig.manualRule.condition = new Condition(newConfig.manualRule.conditionId, "",
zenMode == Global.ZEN_MODE_OFF ? STATE_FALSE : STATE_TRUE,
- origin == UPDATE_ORIGIN_USER ? SOURCE_USER_ACTION : SOURCE_UNKNOWN);
- if (zenMode == Global.ZEN_MODE_OFF && origin != UPDATE_ORIGIN_USER) {
+ origin == ORIGIN_USER_IN_SYSTEMUI ? SOURCE_USER_ACTION : SOURCE_UNKNOWN);
+ if (zenMode == Global.ZEN_MODE_OFF && origin != ORIGIN_USER_IN_SYSTEMUI) {
// User deactivation of DND means just turning off the manual DND rule.
// For API calls (different origin) keep old behavior of snoozing all rules.
for (ZenRule automaticRule : newConfig.automaticRules.values()) {
@@ -1602,7 +1605,7 @@
}
public void setManualZenRuleDeviceEffects(ZenDeviceEffects deviceEffects,
- @ConfigChangeOrigin int origin, String reason, int callingUid) {
+ @ConfigOrigin int origin, String reason, int callingUid) {
if (!Flags.modesUi()) {
return;
}
@@ -1673,7 +1676,11 @@
if (config != null) {
if (forRestore) {
config.user = userId;
- if (!Flags.modesUi()) {
+ if (Flags.modesUi()) {
+ if (config.manualRule != null) {
+ config.manualRule.condition = null; // don't restore transient state
+ }
+ } else {
config.manualRule = null; // don't restore the manual rule
}
}
@@ -1752,7 +1759,7 @@
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
setConfigLocked(config, null,
- forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason,
+ forRestore ? ORIGIN_RESTORE_BACKUP : ORIGIN_INIT, reason,
Process.SYSTEM_UID);
}
}
@@ -1787,7 +1794,7 @@
/**
* Sets the global notification policy used for priority only do not disturb
*/
- public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin,
+ public void setNotificationPolicy(Policy policy, @ConfigOrigin int origin,
int callingUid) {
synchronized (mConfigLock) {
if (policy == null || mConfig == null) return;
@@ -1843,7 +1850,7 @@
}
if (!newConfig.equals(mConfig)) {
- setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ setConfigLocked(newConfig, null, ORIGIN_SYSTEM,
"cleanUpZenRules", Process.SYSTEM_UID);
}
}
@@ -1893,20 +1900,20 @@
@GuardedBy("mConfigLock")
private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
- @ConfigChangeOrigin int origin, String reason, int callingUid) {
+ @ConfigOrigin int origin, String reason, int callingUid) {
return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/,
callingUid);
}
void setConfig(ZenModeConfig config, ComponentName triggeringComponent,
- @ConfigChangeOrigin int origin, String reason, int callingUid) {
+ @ConfigOrigin int origin, String reason, int callingUid) {
synchronized (mConfigLock) {
setConfigLocked(config, triggeringComponent, origin, reason, callingUid);
}
}
@GuardedBy("mConfigLock")
- private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+ private boolean setConfigLocked(ZenModeConfig config, @ConfigOrigin int origin,
String reason, ComponentName triggeringComponent, boolean setRingerMode,
int callingUid) {
final long identity = Binder.clearCallingIdentity();
@@ -1954,7 +1961,7 @@
* If logging is enabled, will also request logging of the outcome of this change if needed.
*/
@GuardedBy("mConfigLock")
- private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin,
+ private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigOrigin int origin,
String reason, boolean setRingerMode, int callingUid) {
final boolean logZenModeEvents = mFlagResolver.isEnabled(
SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
@@ -1963,7 +1970,7 @@
mZenMode, mConfig, mConsolidatedPolicy);
if (!config.equals(mConfig)) {
// Schedule broadcasts. Cannot be sent during boot, though.
- if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) {
+ if (Flags.modesApi() && origin != ORIGIN_INIT) {
for (ZenRule rule : config.automaticRules.values()) {
ZenRule original = mConfig.automaticRules.get(rule.id);
if (original != null) {
@@ -2020,7 +2027,7 @@
@VisibleForTesting
@GuardedBy("mConfigLock")
- protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason,
+ protected void evaluateZenModeLocked(@ConfigOrigin int origin, String reason,
boolean setRingerMode) {
if (DEBUG) Log.d(TAG, "evaluateZenMode");
if (mConfig == null) return;
@@ -2111,7 +2118,7 @@
}
@GuardedBy("mConfigLock")
- private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin,
+ private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigOrigin int origin,
String reason) {
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -2163,7 +2170,7 @@
}
}
- private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) {
+ private void applyConsolidatedDeviceEffects(@ConfigOrigin int source) {
if (!Flags.modesApi()) {
return;
}
@@ -2506,7 +2513,7 @@
}
if (newZen != -1) {
- setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ setManualZenMode(newZen, null, ORIGIN_SYSTEM,
"ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false,
Process.SYSTEM_UID);
}
@@ -2551,7 +2558,7 @@
break;
}
if (newZen != -1) {
- setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ setManualZenMode(newZen, null, ORIGIN_SYSTEM,
"ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID);
}
@@ -2708,15 +2715,33 @@
}
}
- /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */
- private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) {
+ /**
+ * Checks that the {@code origin} supplied to ZenModeHelper rule-management API methods
+ * ({@link #addAutomaticZenRule}, {@link #removeAutomaticZenRule}, etc, makes sense.
+ */
+ private static void checkManageRuleOrigin(String method, @ConfigOrigin int origin) {
if (!Flags.modesApi()) {
return;
}
- checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- || origin == UPDATE_ORIGIN_USER,
- "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or "
- + "UPDATE_ORIGIN_USER for %s, but received '%s'.",
+ checkArgument(origin == ORIGIN_APP || origin == ORIGIN_SYSTEM
+ || origin == ORIGIN_USER_IN_SYSTEMUI,
+ "Expected one of ORIGIN_APP, ORIGIN_SYSTEM, or "
+ + "ORIGIN_USER_IN_SYSTEMUI for %s, but received '%s'.",
+ method, origin);
+ }
+
+ /**
+ * Checks that the {@code origin} supplied to {@link #setAutomaticZenRuleState} overloads makes
+ * sense.
+ */
+ private static void checkSetRuleStateOrigin(String method, @ConfigOrigin int origin) {
+ if (!Flags.modesApi()) {
+ return;
+ }
+ checkArgument(origin == ORIGIN_APP || origin == ORIGIN_USER_IN_APP
+ || origin == ORIGIN_SYSTEM || origin == ORIGIN_USER_IN_SYSTEMUI,
+ "Expected one of ORIGIN_APP, ORIGIN_USER_IN_APP, ORIGIN_SYSTEM, or "
+ + "ORIGIN_USER_IN_SYSTEMUI for %s, but received '%s'.",
method, origin);
}
@@ -2841,7 +2866,7 @@
}
}
- private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
+ private void postApplyDeviceEffects(@ConfigOrigin int origin) {
removeMessages(MSG_APPLY_EFFECTS);
sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0));
}
@@ -2862,7 +2887,7 @@
updateRingerAndAudio(/* shouldApplyToRinger= */ false);
break;
case MSG_APPLY_EFFECTS:
- @ConfigChangeOrigin int origin = msg.arg1;
+ @ConfigOrigin int origin = msg.arg1;
applyConsolidatedDeviceEffects(origin);
break;
}
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 8b72138..29f8243 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -127,7 +127,7 @@
}
// TODO(b/211761016): should we still create the profile dirs?
- if (ps.getPkg() != null && !shouldHaveAppStorage(ps.getPkg())) {
+ if (!shouldHaveAppStorage(ps)) {
Slog.w(TAG, "Skipping preparing app data for " + ps.getPackageName());
return;
}
@@ -172,19 +172,19 @@
}
private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
- @NonNull AndroidPackage pkg, @UserIdInt int userId,
+ @NonNull PackageStateInternal psi, @UserIdInt int userId,
@StorageManager.StorageFlags int flags, boolean maybeMigrateAppData) {
- if (pkg == null) {
+ if (psi == null || psi.getPkg() == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
- if (!shouldHaveAppStorage(pkg)) {
- Slog.w(TAG, "Skipping preparing app data for " + pkg.getPackageName());
+ if (!shouldHaveAppStorage(psi)) {
+ Slog.w(TAG, "Skipping preparing app data for " + psi.getPackageName());
return;
}
final PackageSetting ps;
synchronized (mPm.mLock) {
- ps = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+ ps = mPm.mSettings.getPackageLPr(psi.getPackageName());
}
prepareAppData(batch, ps, Process.INVALID_UID, userId, flags).thenRun(() -> {
// Note: this code block is executed with the Installer lock
@@ -449,7 +449,7 @@
}
if (ps.getUserStateOrDefault(userId).isInstalled()) {
- prepareAppDataAndMigrate(batch, ps.getPkg(), userId, flags, migrateAppData);
+ prepareAppDataAndMigrate(batch, ps, userId, flags, migrateAppData);
preparedCount++;
}
}
@@ -484,8 +484,7 @@
+ " or was deleted without DELETE_KEEP_DATA",
PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_NOT_INSTALLED_FOR_USER);
}
- if (packageState.getPkg() != null
- && !shouldHaveAppStorage(packageState.getPkg())) {
+ if (!shouldHaveAppStorage(packageState)) {
throw PackageManagerException.ofInternalError(
"Package " + packageName + " shouldn't have storage",
PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_SHOULD_NOT_HAVE_STORAGE);
@@ -535,8 +534,7 @@
if (packageStateInternal != null
&& packageStateInternal.getUserStateOrDefault(
UserHandle.USER_SYSTEM).isInstalled()) {
- AndroidPackage pkg = packageStateInternal.getPkg();
- prepareAppDataAndMigrate(batch, pkg,
+ prepareAppDataAndMigrate(batch, packageStateInternal,
UserHandle.USER_SYSTEM, storageFlags, true /* maybeMigrateAppData */);
count++;
}
@@ -637,12 +635,16 @@
}
/**
- * Returns {@code true} if app's internal storage should be created for this {@code pkg}.
+ * Returns {@code true} if app's internal storage should be created for this {@code ps}.
*/
- private boolean shouldHaveAppStorage(AndroidPackage pkg) {
+ private boolean shouldHaveAppStorage(PackageStateInternal ps) {
+ if (ps.getPkg() == null) {
+ // Keeps the legacy behavior
+ return true;
+ }
PackageManager.Property noAppDataProp =
- pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
- return (noAppDataProp == null || !noAppDataProp.getBoolean()) && pkg.getUid() >= 0;
+ ps.getPkg().getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
+ return (noAppDataProp == null || !noAppDataProp.getBoolean()) && ps.getAppId() >= 0;
}
/**
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b56e119..8398ffc 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -690,7 +690,13 @@
public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
final boolean allowSilentUninstall) {
- final int callingUid = Binder.getCallingUid();
+ deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags,
+ Binder.getCallingUid(), allowSilentUninstall);
+ }
+
+ public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
+ final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
+ final int callingUid, final boolean allowSilentUninstall) {
mPm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_PACKAGES, null);
final Computer snapshot = mPm.snapshotComputer();
@@ -720,16 +726,22 @@
final String internalPackageName =
snapshot.resolveInternalPackageName(packageName, versionCode);
- final int uid = Binder.getCallingUid();
if (!isOrphaned(snapshot, internalPackageName)
&& !allowSilentUninstall
&& !isCallerAllowedToSilentlyUninstall(
- snapshot, uid, internalPackageName, userId)) {
+ snapshot, callingUid, internalPackageName, userId)) {
mPm.mHandler.post(() -> {
try {
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
- intent.putExtra(PackageInstaller.EXTRA_CALLBACK, observer.asBinder());
+ intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
+ new PackageManager.UninstallCompleteCallback(observer.asBinder()));
+ if ((deleteFlags & PackageManager.DELETE_ARCHIVE) != 0) {
+ // Delete flags are passed to the uninstaller activity so it can be
+ // preserved in the follow-up uninstall operation after the user
+ // confirmation
+ intent.putExtra(PackageInstaller.EXTRA_DELETE_FLAGS, deleteFlags);
+ }
observer.onUserActionRequired(intent);
} catch (RemoteException re) {
}
@@ -738,7 +750,7 @@
}
final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
final int[] users = deleteAllUsers ? mUserManagerInternal.getUserIds() : new int[]{userId};
- if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) {
+ if (UserHandle.getUserId(callingUid) != userId || (deleteAllUsers && users.length > 1)) {
mPm.mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"deletePackage for user " + userId);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a0d5ea8..98e3e24 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1801,26 +1801,37 @@
oldPackageState.getRestrictUpdateHash());
}
- if (oldPackage != null) {
- // APK should not change its sharedUserId declarations
- final var oldSharedUid = oldPackage.getSharedUserId() != null
- ? oldPackage.getSharedUserId() : "<nothing>";
- final var newSharedUid = parsedPackage.getSharedUserId() != null
- ? parsedPackage.getSharedUserId() : "<nothing>";
- if (!oldSharedUid.equals(newSharedUid)) {
- throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
- "Package " + parsedPackage.getPackageName()
- + " shared user changed from "
- + oldSharedUid + " to " + newSharedUid);
+ // APK should not change its sharedUserId declarations
+ final String oldSharedUid;
+ if (mPm.mSettings.getSharedUserSettingLPr(oldPackageState) != null) {
+ oldSharedUid = mPm.mSettings.getSharedUserSettingLPr(oldPackageState).name;
+ } else {
+ oldSharedUid = "<nothing>";
+ }
+ String newSharedUid = parsedPackage.getSharedUserId() != null
+ ? parsedPackage.getSharedUserId() : "<nothing>";
+ // If the previously installed app version doesn't have sharedUserSetting,
+ // check that the new apk either doesn't have sharedUserId or it is leaving one.
+ // If it contains sharedUserId but it is also leaving it, it's ok to proceed.
+ if (oldSharedUid.equals("<nothing>")) {
+ if (parsedPackage.isLeavingSharedUser()) {
+ newSharedUid = "<nothing>";
}
+ }
- // APK should not re-join shared UID
- if (oldPackage.isLeavingSharedUser()
- && !parsedPackage.isLeavingSharedUser()) {
- throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
- "Package " + parsedPackage.getPackageName()
- + " attempting to rejoin " + newSharedUid);
- }
+ if (!oldSharedUid.equals(newSharedUid)) {
+ throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+ "Package " + parsedPackage.getPackageName()
+ + " shared user changed from "
+ + oldSharedUid + " to " + newSharedUid);
+ }
+
+ // APK should not re-join shared UID
+ if (oldPackageState.isLeavingSharedUser()
+ && !parsedPackage.isLeavingSharedUser()) {
+ throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+ "Package " + parsedPackage.getPackageName()
+ + " attempting to rejoin " + newSharedUid);
}
// In case of rollback, remember per-user/profile install state
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7156795..be6fa14 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1438,7 +1438,8 @@
if (mContext.checkPermission(Manifest.permission.DELETE_PACKAGES, callingPid, callingUid)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
- mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
+ mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags,
+ callingUid);
} else if (canSilentlyInstallPackage) {
// Allow the device owner and affiliated profile owner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 33ca8a8..20859da 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3323,9 +3323,17 @@
}
public void deletePackageVersioned(VersionedPackage versionedPackage,
+ final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
+ final int callingUid) {
+ mDeletePackageHelper.deletePackageVersionedInternal(
+ versionedPackage, observer, userId, deleteFlags, callingUid,
+ /* allowSilentUninstall= */ false);
+ }
+
+ public void deletePackageVersioned(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
mDeletePackageHelper.deletePackageVersionedInternal(
- versionedPackage, observer, userId, deleteFlags, false);
+ versionedPackage, observer, userId, deleteFlags, /* allowSilentUninstall= */ false);
}
boolean isCallerVerifier(@NonNull Computer snapshot, int callingUid) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9f10e01..d374142 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -98,6 +98,7 @@
SCANNED_AS_STOPPED_SYSTEM_APP,
PENDING_RESTORE,
DEBUGGABLE,
+ IS_LEAVING_SHARED_USER,
})
public @interface Flags {
}
@@ -107,6 +108,7 @@
private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
private static final int PENDING_RESTORE = 1 << 4;
private static final int DEBUGGABLE = 1 << 5;
+ private static final int IS_LEAVING_SHARED_USER = 1 << 6;
}
private int mBooleans;
@@ -595,6 +597,20 @@
}
/**
+ * @see PackageState#isLeavingSharedUser
+ */
+ public PackageSetting setLeavingSharedUser(boolean value) {
+ setBoolean(Booleans.IS_LEAVING_SHARED_USER, value);
+ onChanged();
+ return this;
+ }
+
+ @Override
+ public boolean isLeavingSharedUser() {
+ return getBoolean(Booleans.IS_LEAVING_SHARED_USER);
+ }
+
+ /**
* @see AndroidPackage#getBaseRevisionCode
*/
public PackageSetting setBaseRevisionCode(int value) {
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7afc358..26da84f 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -435,7 +435,7 @@
// Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
// app cases
- if (deletedPkg.getSplitNames() != null) {
+ if (deletedPkg != null && deletedPkg.getSplitNames() != null) {
deletedPs.setSplitNames(deletedPkg.getSplitNames());
deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 95561f5f..61fddba 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -482,6 +482,7 @@
+ " to " + volumeUuid);
pkgSetting.setVolumeUuid(volumeUuid);
}
+ pkgSetting.setLeavingSharedUser(parsedPackage.isLeavingSharedUser());
SharedLibraryInfo sdkLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b7dfd8d..55280b4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2585,12 +2585,31 @@
boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true);
if (libName != null && libVersion >= 0) {
+ final int beforeUsesSdkLibrariesLength = outPs.getUsesSdkLibraries().length;
+ // If the lib already exists in the outPs#getUsesSdkLibraries, don't add it
+ // into the array and update its information below
outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class,
outPs.getUsesSdkLibraries(), libName));
- outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
- outPs.getUsesSdkLibrariesVersionsMajor(), libVersion));
- outPs.setUsesSdkLibrariesOptional(ArrayUtils.appendBoolean(
- outPs.getUsesSdkLibrariesOptional(), optional));
+
+ // If the lib has already been added before, update the other information
+ final int afterUsesSdkLibrariesLength = outPs.getUsesSdkLibraries().length;
+ if (beforeUsesSdkLibrariesLength == afterUsesSdkLibrariesLength) {
+ final int index = ArrayUtils.indexOf(outPs.getUsesSdkLibraries(), libName);
+ final long[] usesSdkLibrariesVersionsMajor =
+ outPs.getUsesSdkLibrariesVersionsMajor();
+ usesSdkLibrariesVersionsMajor[index] = libVersion;
+ outPs.setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersionsMajor);
+
+ final boolean[] usesSdkLibrariesOptional = outPs.getUsesSdkLibrariesOptional();
+ usesSdkLibrariesOptional[index] = optional;
+ outPs.setUsesSdkLibrariesOptional(usesSdkLibrariesOptional);
+ } else {
+ outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong(
+ outPs.getUsesSdkLibrariesVersionsMajor(), libVersion,
+ /* allowDuplicates= */ true));
+ outPs.setUsesSdkLibrariesOptional(ArrayUtils.appendBooleanDuplicatesAllowed(
+ outPs.getUsesSdkLibrariesOptional(), optional));
+ }
}
XmlUtils.skipCurrentTag(parser);
@@ -2602,10 +2621,24 @@
long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1);
if (libName != null && libVersion >= 0) {
+ final int beforeUsesStaticLibrariesLength = outPs.getUsesStaticLibraries().length;
+ // If the lib already exists in the outPs#getUsesStaticLibraries, don't add it
+ // into the array and update its information below
outPs.setUsesStaticLibraries(ArrayUtils.appendElement(String.class,
outPs.getUsesStaticLibraries(), libName));
- outPs.setUsesStaticLibrariesVersions(ArrayUtils.appendLong(
- outPs.getUsesStaticLibrariesVersions(), libVersion));
+
+ // If the lib has already been added before, update the version
+ final int afterUsesStaticLibrariesLength = outPs.getUsesStaticLibraries().length;
+ if (beforeUsesStaticLibrariesLength == afterUsesStaticLibrariesLength) {
+ final int index = ArrayUtils.indexOf(outPs.getUsesStaticLibraries(), libName);
+ final long[] usesStaticLibrariesVersions = outPs.getUsesStaticLibrariesVersions();
+ usesStaticLibrariesVersions[index] = libVersion;
+ outPs.setUsesStaticLibrariesVersions(usesStaticLibrariesVersions);
+ } else {
+ outPs.setUsesStaticLibrariesVersions(ArrayUtils.appendLong(
+ outPs.getUsesStaticLibrariesVersions(), libVersion,
+ /* allowDuplicates= */ true));
+ }
}
XmlUtils.skipCurrentTag(parser);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 829ee27..57d7d79 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2105,6 +2105,10 @@
@Override
public void setUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("set user admin");
+ if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+ checkAdminStatusChangeAllowed(userId);
+ }
+
mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN);
UserData user;
synchronized (mPackagesLock) {
@@ -2133,6 +2137,10 @@
@Override
public void revokeUserAdmin(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
+ if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+ checkAdminStatusChangeAllowed(userId);
+ }
+
mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN);
UserData user;
synchronized (mPackagesLock) {
@@ -4065,6 +4073,26 @@
}
}
+ /**
+ * Checks if changing the admin status of a target user is restricted
+ * due to the DISALLOW_GRANT_ADMIN restriction. If either the calling
+ * user or the target user has this restriction, a SecurityException
+ * is thrown.
+ *
+ * @param targetUser The user ID of the user whose admin status is being
+ * considered for change.
+ * @throws SecurityException if the admin status change is restricted due
+ * to the DISALLOW_GRANT_ADMIN restriction.
+ */
+ private void checkAdminStatusChangeAllowed(int targetUser) {
+ if (hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, UserHandle.getCallingUserId())
+ || hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, targetUser)) {
+ throw new SecurityException(
+ "Admin status change is restricted. The DISALLOW_GRANT_ADMIN "
+ + "restriction is applied either on the current or the target user.");
+ }
+ }
+
@GuardedBy({"mPackagesLock"})
private void writeBitmapLP(UserInfo info, Bitmap bitmap) {
try {
@@ -5443,6 +5471,13 @@
enforceUserRestriction(restriction, UserHandle.getCallingUserId(),
"Cannot add user");
+ if (Flags.unicornModeRefactoringForHsumReadOnly()) {
+ if ((flags & UserInfo.FLAG_ADMIN) != 0) {
+ enforceUserRestriction(UserManager.DISALLOW_GRANT_ADMIN,
+ UserHandle.getCallingUserId(), "Cannot create ADMIN user");
+ }
+ }
+
return createUserInternalUnchecked(name, userType, flags, parentId,
/* preCreate= */ false, disallowedPackages, /* token= */ null);
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 5876188..bbc17c8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -488,4 +488,10 @@
* @hide
*/
boolean isScannedAsStoppedSystemApp();
+
+ /**
+ * see AndroidPackage#isLeavingSharedUser()
+ * @hide
+ */
+ boolean isLeavingSharedUser();
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index cefecbc..5a45186 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -30,6 +30,7 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -39,7 +40,6 @@
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -49,8 +49,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
import com.android.internal.util.XmlUtils;
-import com.android.server.input.KeyboardMetricsCollector;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
+import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -61,6 +61,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Manages quick launch shortcuts by:
@@ -123,6 +124,7 @@
private final Context mContext;
private final Handler mHandler;
+ private final InputManagerInternal mInputManagerInternal;
private boolean mSearchKeyShortcutPending = false;
private boolean mConsumeSearchKeyUp = true;
private UserHandle mCurrentUser;
@@ -136,6 +138,7 @@
mRoleIntents.remove(roleName);
}, UserHandle.ALL);
mCurrentUser = currentUser;
+ mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
loadShortcuts();
}
@@ -473,7 +476,7 @@
+ "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
+ " category=" + category + " role=" + role);
}
- logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(intent));
+ notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(intent));
return true;
} else {
return false;
@@ -494,22 +497,19 @@
+ "the activity to which it is registered was not found: "
+ "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
}
- logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(shortcutIntent));
+ notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(shortcutIntent));
return true;
}
return false;
}
- private void logKeyboardShortcut(KeyEvent event, KeyboardLogEvent logEvent) {
- mHandler.post(() -> handleKeyboardLogging(event, logEvent));
- }
-
- private void handleKeyboardLogging(KeyEvent event, KeyboardLogEvent logEvent) {
- final InputManager inputManager = mContext.getSystemService(InputManager.class);
- final InputDevice inputDevice = inputManager != null
- ? inputManager.getInputDevice(event.getDeviceId()) : null;
- KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
- logEvent, event.getMetaState(), event.getKeyCode());
+ private void notifyKeyboardShortcutTriggered(KeyEvent event,
+ @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+ if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+ return;
+ }
+ mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
+ new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
}
/**
@@ -708,6 +708,97 @@
return context.getString(resid);
};
+
+ /**
+ * Find Keyboard shortcut event corresponding to intent filter category. Returns
+ * {@code SYSTEM_SHORTCUT_UNSPECIFIED if no matching event found}
+ */
+ @KeyboardSystemShortcut.SystemShortcut
+ private static int getSystemShortcutFromIntent(Intent intent) {
+ Intent selectorIntent = intent.getSelector();
+ if (selectorIntent != null) {
+ Set<String> selectorCategories = selectorIntent.getCategories();
+ if (selectorCategories != null && !selectorCategories.isEmpty()) {
+ for (String intentCategory : selectorCategories) {
+ int systemShortcut = getEventFromSelectorCategory(intentCategory);
+ if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+ continue;
+ }
+ return systemShortcut;
+ }
+ }
+ }
+
+ // The shortcut may be targeting a system role rather than using an intent selector,
+ // so check for that.
+ String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
+ if (!TextUtils.isEmpty(role)) {
+ return getLogEventFromRole(role);
+ }
+
+ Set<String> intentCategories = intent.getCategories();
+ if (intentCategories == null || intentCategories.isEmpty()
+ || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+ }
+ if (intent.getComponent() == null) {
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+ }
+
+ // TODO(b/280423320): Add new field package name associated in the
+ // KeyboardShortcutEvent atom and log it accordingly.
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+ }
+
+ @KeyboardSystemShortcut.SystemShortcut
+ private static int getEventFromSelectorCategory(String category) {
+ switch (category) {
+ case Intent.CATEGORY_APP_BROWSER:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+ case Intent.CATEGORY_APP_EMAIL:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL;
+ case Intent.CATEGORY_APP_CONTACTS:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS;
+ case Intent.CATEGORY_APP_CALENDAR:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR;
+ case Intent.CATEGORY_APP_CALCULATOR:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR;
+ case Intent.CATEGORY_APP_MUSIC:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC;
+ case Intent.CATEGORY_APP_MAPS:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS;
+ case Intent.CATEGORY_APP_MESSAGING:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+ case Intent.CATEGORY_APP_GALLERY:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY;
+ case Intent.CATEGORY_APP_FILES:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES;
+ case Intent.CATEGORY_APP_WEATHER:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER;
+ case Intent.CATEGORY_APP_FITNESS:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS;
+ default:
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Find KeyboardLogEvent corresponding to the provide system role name.
+ * Returns {@code null} if no matching event found.
+ */
+ @KeyboardSystemShortcut.SystemShortcut
+ private static int getLogEventFromRole(String role) {
+ if (RoleManager.ROLE_BROWSER.equals(role)) {
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+ } else if (RoleManager.ROLE_SMS.equals(role)) {
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+ } else {
+ Log.w(TAG, "Keyboard shortcut to launch "
+ + role + " not supported for logging");
+ return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+ }
+ }
+
void dump(String prefix, PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", prefix);
ipw.println("ModifierShortcutManager shortcuts:");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 21d6c64..720c1c2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioSystem;
@@ -164,8 +165,6 @@
import android.os.Trace;
import android.os.UEventObserver;
import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -229,8 +228,6 @@
import com.android.server.SystemServiceManager;
import com.android.server.UiThread;
import com.android.server.input.InputManagerInternal;
-import com.android.server.input.KeyboardMetricsCollector;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -238,8 +235,6 @@
import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.vibrator.HapticFeedbackVibrationProvider;
-import com.android.server.vibrator.VibratorFrameworkStatsLogger;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -462,7 +457,6 @@
PackageManager mPackageManager;
SideFpsEventHandler mSideFpsEventHandler;
LockPatternUtils mLockPatternUtils;
- private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
private boolean mHasFeatureAuto;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -737,7 +731,6 @@
private static final int MSG_LAUNCH_ASSIST = 23;
private static final int MSG_RINGER_TOGGLE_CHORD = 24;
private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
- private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
private static final int MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE = 27;
private class PolicyHandler extends Handler {
@@ -825,9 +818,6 @@
handleSwitchKeyboardLayout(object.keyEvent, object.direction,
object.focusedToken);
break;
- case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
- handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
- break;
case MSG_SET_DEFERRED_KEY_ACTIONS_EXECUTABLE:
final int keyCode = msg.arg1;
final long downTime = (Long) msg.obj;
@@ -1829,7 +1819,7 @@
}
private void handleShortPressOnHome(KeyEvent event) {
- logKeyboardSystemsEvent(event, KeyboardLogEvent.HOME);
+ notifyKeyboardShortcutTriggered(event, KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME);
// Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
final HdmiControl hdmiControl = getHdmiControl();
@@ -2063,7 +2053,8 @@
}
switch (mDoubleTapOnHomeBehavior) {
case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
- logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
mHomeConsumed = true;
toggleRecentApps();
break;
@@ -2091,19 +2082,23 @@
case LONG_PRESS_HOME_ALL_APPS:
if (mHasFeatureLeanback) {
launchAllAppsAction();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
} else {
launchAllAppsViaA11y();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
}
break;
case LONG_PRESS_HOME_ASSIST:
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
launchAssistAction(null, event.getDeviceId(), event.getEventTime(),
AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
break;
case LONG_PRESS_HOME_NOTIFICATION_PANEL:
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
toggleNotificationPanel();
break;
default:
@@ -2388,8 +2383,6 @@
mContext.registerReceiver(mMultiuserReceiver, filter);
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
- mHapticFeedbackVibrationProvider =
- new HapticFeedbackVibrationProvider(mContext.getResources(), mVibrator);
mGlobalKeyManager = new GlobalKeyManager(mContext);
@@ -3292,39 +3285,29 @@
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
};
- /**
- * Log the keyboard shortcuts without blocking the current thread.
- *
- * We won't log keyboard events when the input device is null
- * or when it is virtual.
- */
- private void handleKeyboardSystemEvent(KeyboardLogEvent keyboardLogEvent, KeyEvent event) {
- final InputDevice inputDevice = mInputManager.getInputDevice(event.getDeviceId());
- KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
- keyboardLogEvent, event.getMetaState(), event.getKeyCode());
- event.recycle();
- }
-
- private void logKeyboardSystemsEventOnActionUp(KeyEvent event,
- KeyboardLogEvent keyboardSystemEvent) {
+ private void notifyKeyboardShortcutTriggeredOnActionUp(KeyEvent event,
+ @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
if (event.getAction() != KeyEvent.ACTION_UP) {
return;
}
- logKeyboardSystemsEvent(event, keyboardSystemEvent);
+ notifyKeyboardShortcutTriggered(event, systemShortcut);
}
- private void logKeyboardSystemsEventOnActionDown(KeyEvent event,
- KeyboardLogEvent keyboardSystemEvent) {
+ private void notifyKeyboardShortcutTriggeredOnActionDown(KeyEvent event,
+ @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return;
}
- logKeyboardSystemsEvent(event, keyboardSystemEvent);
+ notifyKeyboardShortcutTriggered(event, systemShortcut);
}
- private void logKeyboardSystemsEvent(KeyEvent event, KeyboardLogEvent keyboardSystemEvent) {
- KeyEvent eventToLog = KeyEvent.obtain(event);
- mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, keyboardSystemEvent.getIntValue(), 0,
- eventToLog).sendToTarget();
+ private void notifyKeyboardShortcutTriggered(KeyEvent event,
+ @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+ if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+ return;
+ }
+ mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
+ new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
}
@Override
@@ -3434,7 +3417,8 @@
case KeyEvent.KEYCODE_RECENT_APPS:
if (firstDown) {
showRecentApps(false /* triggeredFromAltTab */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
}
return true;
case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3443,7 +3427,8 @@
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
}
}
return true;
@@ -3452,7 +3437,8 @@
launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
deviceId, event.getEventTime(),
AssistUtils.INVOCATION_TYPE_UNKNOWN);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
return true;
}
break;
@@ -3465,14 +3451,16 @@
case KeyEvent.KEYCODE_I:
if (firstDown && event.isMetaPressed()) {
showSystemSettings();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
return true;
}
break;
case KeyEvent.KEYCODE_L:
if (firstDown && event.isMetaPressed()) {
lockNow(null /* options */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LOCK_SCREEN);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN);
return true;
}
break;
@@ -3480,10 +3468,12 @@
if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
sendSystemKeyToStatusBarAsync(event);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_NOTES);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES);
} else {
toggleNotificationPanel();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
}
return true;
}
@@ -3491,7 +3481,8 @@
case KeyEvent.KEYCODE_S:
if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TAKE_SCREENSHOT);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT);
return true;
}
break;
@@ -3504,14 +3495,16 @@
} catch (RemoteException e) {
Slog.d(TAG, "Error taking bugreport", e);
}
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TRIGGER_BUG_REPORT);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT);
return true;
}
}
// fall through
case KeyEvent.KEYCODE_ESCAPE:
if (firstDown && event.isMetaPressed()) {
- logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
injectBackGesture(event.getDownTime());
return true;
}
@@ -3520,7 +3513,8 @@
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event));
- logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION);
return true;
}
}
@@ -3530,7 +3524,8 @@
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event));
- logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE);
return true;
}
}
@@ -3540,12 +3535,15 @@
if (event.isCtrlPressed()) {
moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
true /* leftOrTop */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
} else if (event.isAltPressed()) {
setSplitscreenFocus(true /* leftOrTop */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
} else {
- logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
injectBackGesture(event.getDownTime());
}
return true;
@@ -3556,11 +3554,13 @@
if (event.isCtrlPressed()) {
moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
false /* leftOrTop */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
return true;
} else if (event.isAltPressed()) {
setSplitscreenFocus(false /* leftOrTop */);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
return true;
}
}
@@ -3568,7 +3568,8 @@
case KeyEvent.KEYCODE_SLASH:
if (firstDown && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
- logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_SHORTCUT_HELPER);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER);
return true;
}
break;
@@ -3620,25 +3621,32 @@
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode));
+
+ int systemShortcut = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
+ ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN
+ : KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP;
+ notifyKeyboardShortcutTriggered(event, systemShortcut);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
if (down) {
mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
- logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
if (down) {
mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
- logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP);
}
return true;
case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
// TODO: Add logic
if (!down) {
- logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE);
}
return true;
case KeyEvent.KEYCODE_VOLUME_UP:
@@ -3665,7 +3673,8 @@
if (firstDown && !keyguardOn && isUserSetupComplete()) {
if (event.isMetaPressed()) {
showRecentApps(false);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
return true;
} else if (mRecentAppsHeldModifiers == 0) {
final int shiftlessModifiers =
@@ -3674,7 +3683,8 @@
shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mRecentAppsHeldModifiers = shiftlessModifiers;
showRecentApps(true);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
return true;
}
}
@@ -3687,17 +3697,20 @@
Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
msg.setAsynchronous(true);
msg.sendToTarget();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
} else {
launchAllAppsViaA11y();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
}
}
return true;
case KeyEvent.KEYCODE_NOTIFICATION:
if (!down) {
toggleNotificationPanel();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
}
return true;
case KeyEvent.KEYCODE_SEARCH:
@@ -3705,7 +3718,8 @@
switch (mSearchKeyBehavior) {
case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: {
launchTargetSearchActivity();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SEARCH);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH);
return true;
}
case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH:
@@ -3718,7 +3732,8 @@
if (firstDown) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(event, focusedToken, direction);
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH);
return true;
}
break;
@@ -3737,11 +3752,13 @@
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
} else if (mPendingMetaAction) {
if (!canceled) {
launchAllAppsViaA11y();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
}
mPendingMetaAction = false;
}
@@ -3769,14 +3786,16 @@
if (mPendingCapsLockToggle) {
mInputManagerInternal.toggleCapsLock(event.getDeviceId());
mPendingCapsLockToggle = false;
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
return true;
}
}
break;
case KeyEvent.KEYCODE_CAPS_LOCK:
if (!down) {
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
}
break;
case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
@@ -3790,10 +3809,12 @@
if (firstDown) {
if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
toggleNotificationPanel();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
} else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
showSystemSettings();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
}
}
return true;
@@ -4739,7 +4760,8 @@
// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_BACK: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
if (down) {
// There may have other embedded activities on the same Task. Try to move the
// focus before processing the back event.
@@ -4760,8 +4782,12 @@
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
- logKeyboardSystemsEventOnActionDown(event,
- KeyboardLogEvent.getVolumeEvent(keyCode));
+ int systemShortcut = keyCode == KEYCODE_VOLUME_DOWN
+ ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN
+ : keyCode == KEYCODE_VOLUME_UP
+ ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP
+ : KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE;
+ notifyKeyboardShortcutTriggeredOnActionDown(event, systemShortcut);
if (down) {
sendSystemKeyToStatusBarAsync(event);
@@ -4862,7 +4888,8 @@
}
case KeyEvent.KEYCODE_TV_POWER: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down && hdmiControlManager != null) {
@@ -4872,7 +4899,8 @@
}
case KeyEvent.KEYCODE_POWER: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
@@ -4895,14 +4923,16 @@
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
// fall through
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SYSTEM_NAVIGATION);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION);
result &= ~ACTION_PASS_TO_USER;
interceptSystemNavigationKey(event);
break;
}
case KeyEvent.KEYCODE_SLEEP: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false;
if (!mPowerManager.isInteractive()) {
@@ -4918,7 +4948,8 @@
}
case KeyEvent.KEYCODE_SOFT_SLEEP: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false;
if (!down) {
@@ -4929,7 +4960,8 @@
}
case KeyEvent.KEYCODE_WAKEUP: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.WAKEUP);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP);
result &= ~ACTION_PASS_TO_USER;
isWakeKey = true;
break;
@@ -4938,7 +4970,8 @@
case KeyEvent.KEYCODE_MUTE:
result &= ~ACTION_PASS_TO_USER;
if (down && event.getRepeatCount() == 0) {
- logKeyboardSystemsEvent(event, KeyboardLogEvent.SYSTEM_MUTE);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE);
toggleMicrophoneMuteFromKey();
}
break;
@@ -4953,7 +4986,8 @@
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
- logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.MEDIA_KEY);
+ notifyKeyboardShortcutTriggeredOnActionUp(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY);
if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
// If the global session is active pass all media keys to it
// instead of the active window.
@@ -4998,7 +5032,8 @@
0 /* unused */, event.getEventTime() /* eventTime */);
msg.setAsynchronous(true);
msg.sendToTarget();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
}
result &= ~ACTION_PASS_TO_USER;
break;
@@ -5009,7 +5044,8 @@
Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
msg.setAsynchronous(true);
msg.sendToTarget();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT);
}
result &= ~ACTION_PASS_TO_USER;
break;
@@ -5975,10 +6011,10 @@
public void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
if (safeMode) {
- performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
+ performHapticFeedback(
HapticFeedbackConstants.SAFE_MODE_ENABLED,
- "Safe Mode Enabled", HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
- 0 /* privFlags */);
+ "Safe Mode Enabled" /* reason */,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
}
@@ -6447,9 +6483,13 @@
Settings.Global.THEATER_MODE_ON, 0) == 1;
}
- private boolean performHapticFeedback(int effectId, String reason) {
- return performHapticFeedback(Process.myUid(), mContext.getOpPackageName(),
- effectId, reason, 0 /* flags */, 0 /* privFlags */);
+ private void performHapticFeedback(int effectId, String reason) {
+ performHapticFeedback(effectId, reason, 0 /* flags */);
+ }
+
+ private void performHapticFeedback(
+ int effectId, String reason, @HapticFeedbackConstants.Flags int flags) {
+ mVibrator.performHapticFeedback(effectId, reason, flags, 0 /* privFlags */);
}
@Override
@@ -6457,25 +6497,6 @@
return mGlobalKeyManager.shouldHandleGlobalKey(keyCode);
}
- @Override
- public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
- int flags, int privFlags) {
- if (!mVibrator.hasVibrator()) {
- return false;
- }
- VibrationEffect effect =
- mHapticFeedbackVibrationProvider.getVibrationForHapticFeedback(effectId);
- if (effect == null) {
- return false;
- }
- VibrationAttributes attrs =
- mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
- effectId, flags, privFlags);
- VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
- mVibrator.vibrate(uid, packageName, effect, reason, attrs);
- return true;
- }
-
@Override
public void keepScreenOnStartedLw() {
@@ -6651,7 +6672,6 @@
pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
pw.print(prefix); pw.print("mKidsModeEnabled="); pw.println(mKidsModeEnabled);
- mHapticFeedbackVibrationProvider.dump(prefix, pw);
mGlobalKeyManager.dump(prefix, pw);
mKeyCombinationManager.dump(prefix, pw);
mSingleKeyGestureDetector.dump(prefix, pw);
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 1b394f6..67f5f27 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -80,7 +80,6 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
-import android.view.HapticFeedbackConstants;
import android.view.IDisplayFoldListener;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
@@ -1077,13 +1076,6 @@
public void enableScreenAfterBoot();
/**
- * Call from application to perform haptic feedback on its window.
- */
- public boolean performHapticFeedback(int uid, String packageName, int effectId,
- String reason, @HapticFeedbackConstants.Flags int flags,
- @HapticFeedbackConstants.PrivateFlags int privFlags);
-
- /**
* Called when we have started keeping the screen on because a window
* requesting this has become visible.
*/
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 503a726..65fc7b2 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -18,6 +18,7 @@
import android.annotation.Nullable;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.os.vibrator.Flags;
@@ -28,6 +29,7 @@
import android.util.SparseArray;
import android.util.Xml;
+import com.android.internal.util.XmlUtils;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
import com.android.internal.vibrator.persistence.XmlValidator;
@@ -39,6 +41,7 @@
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.io.Reader;
/**
* Class that loads custom {@link VibrationEffect} to be performed for each
@@ -127,27 +130,19 @@
Slog.d(TAG, "Haptic feedback customization feature is not enabled.");
return null;
}
- String customizationFile =
- res.getString(
- com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
- if (TextUtils.isEmpty(customizationFile)) {
- Slog.d(TAG, "Customization file not configured.");
+
+ // Old loading path that reads customization from file at dir defined by config.
+ TypedXmlPullParser parser = readCustomizationFile(res);
+ if (parser == null) {
+ // When old loading path doesn't succeed, try loading customization from resources.
+ parser = readCustomizationResources(res);
+ }
+ if (parser == null) {
+ Slog.d(TAG, "No loadable haptic feedback customization.");
return null;
}
- FileReader fileReader;
- try {
- fileReader = new FileReader(customizationFile);
- } catch (FileNotFoundException e) {
- Slog.d(TAG, "Specified customization file not found.");
- return null;
- }
-
- TypedXmlPullParser parser = Xml.newFastPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(fileReader);
-
- XmlReader.readDocumentStartTag(parser, TAG_CONSTANTS);
+ XmlUtils.beginDocument(parser, TAG_CONSTANTS);
XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
int rootDepth = parser.getDepth();
@@ -191,6 +186,46 @@
return mapping;
}
+ // TODO(b/356412421): deprecate old path related files.
+ private static TypedXmlPullParser readCustomizationFile(Resources res)
+ throws XmlPullParserException {
+ String customizationFile = res.getString(
+ com.android.internal.R.string.config_hapticFeedbackCustomizationFile);
+ if (TextUtils.isEmpty(customizationFile)) {
+ return null;
+ }
+
+ final Reader customizationReader;
+ try {
+ customizationReader = new FileReader(customizationFile);
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Specified customization file not found.", e);
+ return null;
+ }
+
+ final TypedXmlPullParser parser;
+ parser = Xml.newFastPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(customizationReader);
+ Slog.d(TAG, "Successfully opened customization file.");
+ return parser;
+ }
+
+ private static TypedXmlPullParser readCustomizationResources(Resources res) {
+ if (!Flags.loadHapticFeedbackVibrationCustomizationFromResources()) {
+ return null;
+ }
+ final XmlResourceParser resParser;
+ try {
+ resParser = res.getXml(com.android.internal.R.xml.haptic_feedback_customization);
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Haptic customization resource not found.", e);
+ return null;
+ }
+ Slog.d(TAG, "Successfully opened customization resource.");
+ return XmlUtils.makeTyped(resParser);
+ }
+
/**
* Represents an error while parsing a haptic feedback customization XML.
*/
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 3f9da82..eccbffb 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -22,7 +22,6 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
import android.util.Slog;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
@@ -348,7 +347,7 @@
predefinedEffectId = VibrationEffect.EFFECT_CLICK;
predefinedEffectFallback = true;
}
- if (Flags.keyboardCategoryEnabled() && mKeyboardVibrationFixedAmplitude > 0) {
+ if (mKeyboardVibrationFixedAmplitude > 0) {
if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
return VibrationEffect.startComposition()
.addPrimitive(primitiveId, mKeyboardVibrationFixedAmplitude)
@@ -361,10 +360,6 @@
private VibrationAttributes createKeyboardVibrationAttributes(
@HapticFeedbackConstants.PrivateFlags int privFlags) {
- // Use touch attribute when the keyboard category is disable.
- if (!Flags.keyboardCategoryEnabled()) {
- return TOUCH_VIBRATION_ATTRIBUTES;
- }
// Use touch attribute when the haptic is not apply to IME.
if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
return TOUCH_VIBRATION_ATTRIBUTES;
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 3933759..a74c4e0 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,7 +17,6 @@
package com.android.server.vibrator;
import android.annotation.NonNull;
-import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.os.ExternalVibrationScale;
import android.os.VibrationAttributes;
@@ -25,6 +24,7 @@
import android.os.Vibrator;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationConfig;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -37,8 +37,11 @@
final class VibrationScaler {
private static final String TAG = "VibrationScaler";
+ // TODO(b/345186129): remove this once we finish migrating to scale factor and clean up flags.
// Scale levels. Each level, except MUTE, is defined as the delta between the current setting
// and the default intensity for that type of vibration (i.e. current - default).
+ // It's important that we apply the scaling on the delta between the two so
+ // that the default intensity level applies no scaling to application provided effects.
static final int SCALE_VERY_LOW = ExternalVibrationScale.ScaleLevel.SCALE_VERY_LOW; // -2
static final int SCALE_LOW = ExternalVibrationScale.ScaleLevel.SCALE_LOW; // -1
static final int SCALE_NONE = ExternalVibrationScale.ScaleLevel.SCALE_NONE; // 0
@@ -53,35 +56,15 @@
private static final float SCALE_FACTOR_HIGH = 1.2f;
private static final float SCALE_FACTOR_VERY_HIGH = 1.4f;
- private static final ScaleLevel SCALE_LEVEL_NONE = new ScaleLevel(SCALE_FACTOR_NONE);
-
- // A mapping from the intensity adjustment to the scaling to apply, where the intensity
- // adjustment is defined as the delta between the default intensity level and the user selected
- // intensity level. It's important that we apply the scaling on the delta between the two so
- // that the default intensity level applies no scaling to application provided effects.
- private final SparseArray<ScaleLevel> mScaleLevels;
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
+ private final float mDefaultVibrationScaleLevelGain;
private final SparseArray<Float> mAdaptiveHapticsScales = new SparseArray<>();
- VibrationScaler(Context context, VibrationSettings settingsController) {
+ VibrationScaler(VibrationConfig config, VibrationSettings settingsController) {
mSettingsController = settingsController;
- mDefaultVibrationAmplitude = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultVibrationAmplitude);
-
- mScaleLevels = new SparseArray<>();
- mScaleLevels.put(SCALE_VERY_LOW, new ScaleLevel(SCALE_FACTOR_VERY_LOW));
- mScaleLevels.put(SCALE_LOW, new ScaleLevel(SCALE_FACTOR_LOW));
- mScaleLevels.put(SCALE_NONE, SCALE_LEVEL_NONE);
- mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_FACTOR_HIGH));
- mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_FACTOR_VERY_HIGH));
- }
-
- /**
- * Returns the default vibration amplitude configured for this device, value in [1,255].
- */
- public int getDefaultVibrationAmplitude() {
- return mDefaultVibrationAmplitude;
+ mDefaultVibrationAmplitude = config.getDefaultVibrationAmplitude();
+ mDefaultVibrationScaleLevelGain = config.getDefaultVibrationScaleLevelGain();
}
/**
@@ -111,6 +94,16 @@
}
/**
+ * Calculates the scale factor to be applied to a vibration with given usage.
+ *
+ * @param usageHint one of VibrationAttributes.USAGE_*
+ * @return The scale factor.
+ */
+ public float getScaleFactor(int usageHint) {
+ return scaleLevelToScaleFactor(getScaleLevel(usageHint));
+ }
+
+ /**
* Returns the adaptive haptics scale that should be applied to the vibrations with
* the given usage. When no adaptive scales are available for the usages, then returns 1
* indicating no scaling will be applied
@@ -135,20 +128,12 @@
@NonNull
public VibrationEffect scale(@NonNull VibrationEffect effect, int usageHint) {
int newEffectStrength = getEffectStrength(usageHint);
- ScaleLevel scaleLevel = mScaleLevels.get(getScaleLevel(usageHint));
+ float scaleFactor = getScaleFactor(usageHint);
float adaptiveScale = getAdaptiveHapticsScale(usageHint);
- if (scaleLevel == null) {
- // Something about our scaling has gone wrong, so just play with no scaling.
- Slog.e(TAG, "No configured scaling level found! (current="
- + mSettingsController.getCurrentIntensity(usageHint) + ", default= "
- + mSettingsController.getDefaultIntensity(usageHint) + ")");
- scaleLevel = SCALE_LEVEL_NONE;
- }
-
return effect.resolve(mDefaultVibrationAmplitude)
.applyEffectStrength(newEffectStrength)
- .scale(scaleLevel.factor)
+ .scale(scaleFactor)
.scaleLinearly(adaptiveScale);
}
@@ -192,14 +177,11 @@
void dump(IndentingPrintWriter pw) {
pw.println("VibrationScaler:");
pw.increaseIndent();
- pw.println("defaultVibrationAmplitude = " + mDefaultVibrationAmplitude);
pw.println("ScaleLevels:");
pw.increaseIndent();
- for (int i = 0; i < mScaleLevels.size(); i++) {
- int scaleLevelKey = mScaleLevels.keyAt(i);
- ScaleLevel scaleLevel = mScaleLevels.valueAt(i);
- pw.println(scaleLevelToString(scaleLevelKey) + " = " + scaleLevel);
+ for (int level = SCALE_VERY_LOW; level <= SCALE_VERY_HIGH; level++) {
+ pw.println(scaleLevelToString(level) + " = " + scaleLevelToScaleFactor(level));
}
pw.decreaseIndent();
@@ -224,16 +206,24 @@
@Override
public String toString() {
+ StringBuilder scaleLevelsStr = new StringBuilder("{");
+ for (int level = SCALE_VERY_LOW; level <= SCALE_VERY_HIGH; level++) {
+ scaleLevelsStr.append(scaleLevelToString(level))
+ .append("=").append(scaleLevelToScaleFactor(level));
+ if (level < SCALE_FACTOR_VERY_HIGH) {
+ scaleLevelsStr.append(", ");
+ }
+ }
+ scaleLevelsStr.append("}");
+
return "VibrationScaler{"
- + "mScaleLevels=" + mScaleLevels
- + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude
+ + "mScaleLevels=" + scaleLevelsStr
+ ", mAdaptiveHapticsScales=" + mAdaptiveHapticsScales
+ '}';
}
private int getEffectStrength(int usageHint) {
int currentIntensity = mSettingsController.getCurrentIntensity(usageHint);
-
if (currentIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
// Bypassing user settings, or it has changed between checking and scaling. Use default.
currentIntensity = mSettingsController.getDefaultIntensity(usageHint);
@@ -244,17 +234,44 @@
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
private static int intensityToEffectStrength(int intensity) {
- switch (intensity) {
- case Vibrator.VIBRATION_INTENSITY_LOW:
- return EffectStrength.LIGHT;
- case Vibrator.VIBRATION_INTENSITY_MEDIUM:
- return EffectStrength.MEDIUM;
- case Vibrator.VIBRATION_INTENSITY_HIGH:
- return EffectStrength.STRONG;
- default:
+ return switch (intensity) {
+ case Vibrator.VIBRATION_INTENSITY_LOW -> EffectStrength.LIGHT;
+ case Vibrator.VIBRATION_INTENSITY_MEDIUM -> EffectStrength.MEDIUM;
+ case Vibrator.VIBRATION_INTENSITY_HIGH -> EffectStrength.STRONG;
+ default -> {
Slog.w(TAG, "Got unexpected vibration intensity: " + intensity);
- return EffectStrength.STRONG;
+ yield EffectStrength.STRONG;
+ }
+ };
+ }
+
+ /** Mapping of ExternalVibrationScale.ScaleLevel.SCALE_* values to scale factor. */
+ private float scaleLevelToScaleFactor(int level) {
+ if (Flags.hapticsScaleV2Enabled()) {
+ if (level == SCALE_NONE || level < SCALE_VERY_LOW || level > SCALE_VERY_HIGH) {
+ // Scale set to none or to a bad value, use default factor for no scaling.
+ return SCALE_FACTOR_NONE;
+ }
+ float scaleFactor = (float) Math.pow(mDefaultVibrationScaleLevelGain, level);
+ if (scaleFactor <= 0) {
+ // Something about our scaling has gone wrong, so just play with no scaling.
+ Slog.wtf(TAG, String.format(Locale.ROOT, "Error in scaling calculations, ended up"
+ + " with invalid scale factor %.2f for scale level %s and default"
+ + " level gain of %.2f", scaleFactor, scaleLevelToString(level),
+ mDefaultVibrationScaleLevelGain));
+ scaleFactor = SCALE_FACTOR_NONE;
+ }
+ return scaleFactor;
}
+
+ return switch (level) {
+ case SCALE_VERY_LOW -> SCALE_FACTOR_VERY_LOW;
+ case SCALE_LOW -> SCALE_FACTOR_LOW;
+ case SCALE_HIGH -> SCALE_FACTOR_HIGH;
+ case SCALE_VERY_HIGH -> SCALE_FACTOR_VERY_HIGH;
+ // Scale set to none or to a bad value, use default factor for no scaling.
+ default -> SCALE_FACTOR_NONE;
+ };
}
static String scaleLevelToString(int scaleLevel) {
@@ -267,18 +284,4 @@
default -> String.valueOf(scaleLevel);
};
}
-
- /** Represents the scale that must be applied to a vibration effect intensity. */
- private static final class ScaleLevel {
- public final float factor;
-
- ScaleLevel(float factor) {
- this.factor = factor;
- }
-
- @Override
- public String toString() {
- return "ScaleLevel{factor=" + factor + "}";
- }
- }
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index fb92d60..f2f5eda 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -55,7 +55,6 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.Vibrator.VibrationIntensity;
-import android.os.vibrator.Flags;
import android.os.vibrator.VibrationConfig;
import android.provider.Settings;
import android.util.IndentingPrintWriter;
@@ -533,8 +532,7 @@
return false;
}
- if (Flags.keyboardCategoryEnabled()
- && mVibrationConfig.isKeyboardVibrationSettingsSupported()) {
+ if (mVibrationConfig.isKeyboardVibrationSettingsSupported()) {
int category = callerInfo.attrs.getCategory();
if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
// Keyboard touch has a different user setting.
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 7610d7d..f2ad5b9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -57,6 +57,7 @@
import android.os.VibratorInfo;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.VibratorInfoFactory;
import android.os.vibrator.persistence.ParsedVibration;
@@ -251,8 +252,9 @@
mHandler = injector.createHandler(Looper.myLooper());
mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
- mVibrationSettings = new VibrationSettings(mContext, mHandler);
- mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+ VibrationConfig vibrationConfig = new VibrationConfig(context.getResources());
+ mVibrationSettings = new VibrationSettings(mContext, mHandler, vibrationConfig);
+ mVibrationScaler = new VibrationScaler(vibrationConfig, mVibrationSettings);
mVibratorControlService = new VibratorControlService(mContext,
injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
mFrameworkStatsLogger, mLock);
@@ -467,6 +469,14 @@
this, flags, privFlags);
}
+ @Override // Binder call
+ public void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
+ int constant, int inputDeviceId, int inputSource, String reason, int flags,
+ int privFlags) {
+ performHapticFeedbackForInputDeviceInternal(uid, deviceId, opPkg, constant, inputDeviceId,
+ inputSource, reason, /* token= */ this, flags, privFlags);
+ }
+
/**
* An internal-only version of performHapticFeedback that allows the caller access to the
* {@link HalVibration}.
@@ -501,6 +511,24 @@
}
/**
+ * An internal-only version of performHapticFeedback that allows the caller access to the
+ * {@link HalVibration}.
+ * The Vibration is only returned if it is ongoing after this method returns.
+ */
+ @VisibleForTesting
+ @Nullable
+ HalVibration performHapticFeedbackForInputDeviceInternal(
+ int uid, int deviceId, String opPkg, int constant, int inputDeviceId, int inputSource,
+ String reason, IBinder token, int flags, int privFlags) {
+ // TODO(b/355543835): implement input device specific logic.
+ if (DEBUG) {
+ Slog.d(TAG, "performHapticFeedbackForInput: input device specific not implemented.");
+ }
+ return performHapticFeedbackInternal(uid, deviceId, opPkg, constant, reason, /* token= */
+ this, flags, privFlags);
+ }
+
+ /**
* An internal-only version of vibrate that allows the caller access to the
* {@link HalVibration}.
* The Vibration is only returned if it is ongoing after this method returns.
@@ -1672,7 +1700,7 @@
IBinder.DeathRecipient {
public final ExternalVibration externalVibration;
- public ExternalVibrationScale scale = new ExternalVibrationScale();
+ public final ExternalVibrationScale scale = new ExternalVibrationScale();
private Vibration.Status mStatus;
@@ -1686,8 +1714,18 @@
mStatus = Vibration.Status.RUNNING;
}
+ public void muteScale() {
+ scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+ if (Flags.hapticsScaleV2Enabled()) {
+ scale.scaleFactor = 0;
+ }
+ }
+
public void scale(VibrationScaler scaler, int usage) {
scale.scaleLevel = scaler.getScaleLevel(usage);
+ if (Flags.hapticsScaleV2Enabled()) {
+ scale.scaleFactor = scaler.getScaleFactor(usage);
+ }
scale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
stats.reportAdaptiveScale(scale.adaptiveHapticsScale);
}
@@ -2021,7 +2059,7 @@
// Create Vibration.Stats as close to the received request as possible, for tracking.
ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
// Mute the request until we run all the checks and accept the vibration.
- vibHolder.scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+ vibHolder.muteScale();
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
@@ -2120,7 +2158,7 @@
new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
/* continueExternalControl= */ false);
// Mute the request, vibration will be ignored.
- vibHolder.scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
+ vibHolder.muteScale();
}
return vibHolder.scale;
}
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 68f3738..19eba5f 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -330,6 +330,7 @@
builder.setIsTranslucent(isTranslucent);
builder.setWindowingMode(source.getWindowingMode());
builder.setAppearance(mainWindow.mAttrs.insetsFlags.appearance);
+ builder.setUiMode(activity.getConfiguration().uiMode);
final Configuration taskConfig = activity.getTask().getConfiguration();
final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
@@ -448,7 +449,8 @@
mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
contentInsets, letterboxInsets, false /* isLowResolution */,
false /* isRealSnapshot */, source.getWindowingMode(),
- attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */);
+ attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */,
+ topActivity.getConfiguration().uiMode /* uiMode */);
return validateSnapshot(taskSnapshot);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7210098..5c096ec 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2524,8 +2524,11 @@
// trampoline that will be always created and finished immediately. Then give a chance to
// see if the snapshot is usable for the current running activity so the transition will
// look smoother, instead of showing a splash screen on the second launch.
- if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null
- && mActivityComponent.equals(task.intent.getComponent())) {
+ if (!newTask && taskSwitch && !activityCreated && task.intent != null
+ // Another case where snapshot is allowed to be used is if this activity has not yet
+ // been created && is translucent or floating.
+ // The component isn't necessary to be matched in this case.
+ && (!mOccludesParent || mActivityComponent.equals(task.intent.getComponent()))) {
final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess);
if (topAttached != null) {
if (topAttached.isSnapshotCompatible(snapshot)
@@ -5463,6 +5466,7 @@
}
}
+ mAtmService.mBackNavigationController.onAppVisibilityChanged(this, visible);
onChildVisibilityRequested(visible);
final DisplayContent displayContent = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2f6e7de..18aa9a0 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1621,8 +1621,10 @@
currentTop, currentTop.mDisplayContent, false /* deferResume */);
}
- if (!avoidMoveToFront() && mDoResume && mRootWindowContainer
- .hasVisibleWindowAboveButDoesNotOwnNotificationShade(started.launchedFromUid)) {
+ if (!avoidMoveToFront() && mDoResume
+ && !mService.getUserManagerInternal().isVisibleBackgroundFullUser(started.mUserId)
+ && mRootWindowContainer.hasVisibleWindowAboveButDoesNotOwnNotificationShade(
+ started.launchedFromUid)) {
// If the UID launching the activity has a visible window on top of the notification
// shade and it's launching an activity that's going to be at the front, we should move
// the shade out of the way so the user can see it. We want to avoid the case where the
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9f3bbd1..e4cae58 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -283,6 +283,7 @@
import com.android.server.am.UserState;
import com.android.server.firewall.IntentFirewall;
import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
@@ -382,6 +383,7 @@
private PermissionPolicyInternal mPermissionPolicyInternal;
private StatusBarManagerInternal mStatusBarManagerInternal;
private WallpaperManagerInternal mWallpaperManagerInternal;
+ private UserManagerInternal mUserManagerInternal;
@VisibleForTesting
final ActivityTaskManagerInternal mInternal;
private PowerManagerInternal mPowerManagerInternal;
@@ -5464,6 +5466,13 @@
return mWallpaperManagerInternal;
}
+ UserManagerInternal getUserManagerInternal() {
+ if (mUserManagerInternal == null) {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ }
+ return mUserManagerInternal;
+ }
+
AppWarnings getAppWarningsLocked() {
return mAppWarnings;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 509a060..8ef2693 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2846,6 +2846,11 @@
} finally {
SaferIntentUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
synchronized (mService.mGlobalLock) {
+ // Remove the empty task in case the activity was failed to be launched on the
+ // task that was restored from Recents.
+ if (!task.hasChild() && task.shouldRemoveSelfOnLastChildRemoval()) {
+ task.removeIfPossible("start-from-recents");
+ }
mService.continueWindowLayout();
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
index b9bdc32..caff96b 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -35,7 +35,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
/**
@@ -112,12 +111,10 @@
: mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
}
- @VisibleForTesting
boolean isHorizontalReachabilityEnabled() {
return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
}
- @VisibleForTesting
boolean isVerticalReachabilityEnabled() {
return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index 90bfddb..c3bf116 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -31,6 +31,8 @@
import android.annotation.Nullable;
import android.graphics.Rect;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.function.Supplier;
/**
@@ -43,7 +45,8 @@
@NonNull
private final AppCompatConfiguration mAppCompatConfiguration;
@Nullable
- private Supplier<Rect> mLetterboxInnerBoundsSupplier;
+ @VisibleForTesting
+ Supplier<Rect> mLetterboxInnerBoundsSupplier;
AppCompatReachabilityPolicy(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration) {
diff --git a/services/core/java/com/android/server/wm/AppSnapshotLoader.java b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
index ed65a2b..5b697e5 100644
--- a/services/core/java/com/android/server/wm/AppSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
@@ -203,7 +203,7 @@
new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop,
proto.letterboxInsetRight, proto.letterboxInsetBottom),
loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
- proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
+ proto.appearance, proto.isTranslucent, false /* hasImeSurface */, proto.uiMode);
} catch (IOException e) {
Slog.w(TAG, "Unable to load task snapshot data for Id=" + id);
return null;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 48e1079..06e665e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -768,6 +768,48 @@
}
}
+ void onAppVisibilityChanged(@NonNull ActivityRecord ar, boolean visible) {
+ if (!mAnimationHandler.mComposed) {
+ return;
+ }
+
+ final boolean openingTransition = mAnimationHandler.mOpenAnimAdaptor
+ .mPreparedOpenTransition != null;
+ // Detect if another transition is collecting during predictive back animation.
+ if (openingTransition && !visible && mAnimationHandler.isTarget(ar, false /* open */)
+ && ar.mTransitionController.isCollecting(ar)) {
+ final TransitionController controller = ar.mTransitionController;
+ boolean collectTask = false;
+ ActivityRecord changedActivity = null;
+ for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
+ final ActivityRecord next = mAnimationHandler.mOpenActivities[i];
+ if (next.mLaunchTaskBehind) {
+ // collect previous activity, so shell side can handle the transition.
+ controller.collect(next);
+ collectTask = true;
+ restoreLaunchBehind(next, true /* cancel */, false /* finishTransition */);
+ changedActivity = next;
+ }
+ }
+ if (collectTask && mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].mSwitchType
+ == AnimationHandler.TASK_SWITCH) {
+ final Task topTask = mAnimationHandler.mOpenAnimAdaptor.mAdaptors[0].getTopTask();
+ if (topTask != null) {
+ WindowContainer parent = mAnimationHandler.mOpenActivities[0].getParent();
+ while (parent != topTask && parent.isDescendantOf(topTask)) {
+ controller.collect(parent);
+ parent = parent.getParent();
+ }
+ controller.collect(topTask);
+ }
+ }
+ if (changedActivity != null) {
+ changedActivity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
+ true /* notifyClients */);
+ }
+ }
+ }
+
// For shell transition
/**
* Check whether the transition targets was animated by back gesture animation.
@@ -784,7 +826,13 @@
mAnimationHandler.markStartingSurfaceMatch(startTransaction);
return;
}
- if (!isMonitoringFinishTransition() || targets.isEmpty()) {
+ if (targets.isEmpty()) {
+ return;
+ }
+ final boolean migratePredictToTransition = Flags.migratePredictiveBackTransition();
+ if (migratePredictToTransition && !mAnimationHandler.mComposed) {
+ return;
+ } else if (!isMonitoringFinishTransition()) {
return;
}
if (mAnimationHandler.hasTargetDetached()) {
@@ -808,20 +856,27 @@
mTmpCloseApps.add(wc);
}
}
- final boolean matchAnimationTargets = isWaitBackTransition()
+ final boolean matchAnimationTargets;
+ if (migratePredictToTransition) {
+ matchAnimationTargets =
+ mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+ } else {
+ matchAnimationTargets = isWaitBackTransition()
&& (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
&& mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+ }
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
- if (!matchAnimationTargets) {
+ // Don't cancel transition, let transition handler to handle it
+ if (!matchAnimationTargets && !migratePredictToTransition) {
mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
} else {
if (mAnimationHandler.mPrepareCloseTransition != null) {
Slog.e(TAG, "Gesture animation is applied on another transition?");
}
mAnimationHandler.mPrepareCloseTransition = transition;
- if (!Flags.migratePredictiveBackTransition()) {
+ if (!migratePredictToTransition) {
// Because the target will reparent to transition root, so it cannot be controlled
// by animation leash. Hide the close target when transition starts.
startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
@@ -839,7 +894,19 @@
}
boolean isMonitorTransitionTarget(WindowContainer wc) {
- if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
+ if (Flags.migratePredictiveBackTransition()) {
+ if (!mAnimationHandler.mComposed) {
+ return false;
+ }
+ if (mAnimationHandler.mSwitchType == AnimationHandler.TASK_SWITCH
+ && wc.asActivityRecord() != null
+ || (mAnimationHandler.mSwitchType == AnimationHandler.ACTIVITY_SWITCH
+ && wc.asTask() != null)) {
+ return false;
+ }
+ return (mAnimationHandler.isTarget(wc, true /* open */)
+ || mAnimationHandler.isTarget(wc, false /* open */));
+ } else if ((isWaitBackTransition() && mAnimationHandler.mPrepareCloseTransition != null)
|| (mAnimationHandler.mOpenAnimAdaptor != null
&& mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition != null)) {
return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
@@ -1840,6 +1907,42 @@
return openActivities;
}
+ boolean restoreBackNavigation() {
+ if (!mAnimationHandler.mComposed) {
+ return false;
+ }
+ ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
+ boolean changed = false;
+ if (penActivities != null) {
+ for (int i = penActivities.length - 1; i >= 0; --i) {
+ ActivityRecord resetActivity = penActivities[i];
+ if (resetActivity.mLaunchTaskBehind) {
+ resetActivity.mTransitionController.collect(resetActivity);
+ restoreLaunchBehind(resetActivity, true, false);
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ }
+
+ boolean restoreBackNavigationSetTransitionReady(Transition transition) {
+ if (!mAnimationHandler.mComposed) {
+ return false;
+ }
+ ActivityRecord[] penActivities = mAnimationHandler.mOpenActivities;
+ if (penActivities != null) {
+ for (int i = penActivities.length - 1; i >= 0; --i) {
+ ActivityRecord resetActivity = penActivities[i];
+ if (transition.isInTransition(resetActivity)) {
+ transition.setReady(resetActivity.getDisplayContent(), true);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private static Transition setLaunchBehind(@NonNull ActivityRecord[] activities) {
final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
final ArrayList<ActivityRecord> affects = new ArrayList<>();
@@ -1887,18 +1990,12 @@
activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */);
}
}
- boolean needTransition = false;
- final DisplayContent dc = affects.get(0).getDisplayContent();
- for (int i = affects.size() - 1; i >= 0; --i) {
- final ActivityRecord activity = affects.get(i);
- needTransition |= tc.isCollecting(activity);
- }
if (prepareOpen != null) {
- if (needTransition) {
+ if (prepareOpen.hasChanges()) {
tc.requestStartTransition(prepareOpen,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
- tc.setReady(dc);
+ prepareOpen.setReady(affects.get(0), true);
return prepareOpen;
} else {
prepareOpen.abort();
@@ -1919,9 +2016,12 @@
activity);
if (cancel) {
final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
- if (migrateBackTransition && finishTransition) {
- activity.commitVisibility(false /* visible */, false /* performLayout */,
- true /* fromTransition */);
+ // could be visible if transition is canceled due to top activity is finishing.
+ if (migrateBackTransition) {
+ if (finishTransition && !activity.shouldBeVisible()) {
+ activity.commitVisibility(false /* visible */, false /* performLayout */,
+ true /* fromTransition */);
+ }
} else {
// Restore the launch-behind state
// TODO b/347168362 Change status directly during collecting for a transition.
@@ -2015,7 +2115,7 @@
return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
}
- static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+ static boolean isSnapshotCompatible(@Nullable TaskSnapshot snapshot,
@NonNull ActivityRecord[] visibleOpenActivities) {
if (snapshot == null) {
return false;
@@ -2026,6 +2126,12 @@
if (!ar.isSnapshotOrientationCompatible(snapshot)) {
return false;
}
+ final int appNightMode = ar.getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ final int snapshotNightMode = snapshot.getUiMode() & Configuration.UI_MODE_NIGHT_MASK;
+ if (appNightMode != snapshotNightMode) {
+ return false;
+ }
oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
}
return oneComponentMatch;
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index f566df5..8f1828d 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -109,6 +109,13 @@
if (!DesktopModeFlagsUtil.DYNAMIC_INITIAL_BOUNDS.isEnabled(activity.mWmService.mContext)) {
return centerInScreen(idealSize, screenBounds);
}
+ if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+ .hasFullscreenOverride()) {
+ // If the activity has a fullscreen override applied, it should be treated as
+ // resizeable and match the device orientation. Thus the ideal size can be
+ // applied.
+ return centerInScreen(idealSize, screenBounds);
+ }
// TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
float appAspectRatio = calculateAspectRatio(task, activity);
final float tdaWidth = stableBounds.width();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9c8c759..fcc6b11 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1804,9 +1804,7 @@
return;
}
final int displayRotation = getRotation();
- final int rotation = ar.isVisible()
- ? ar.getWindowConfiguration().getDisplayRotation()
- : mDisplayRotation.rotationForOrientation(orientation, displayRotation);
+ final int rotation = mDisplayRotation.rotationForOrientation(orientation, displayRotation);
if (rotation == displayRotation) {
return;
}
@@ -6710,6 +6708,11 @@
final boolean rotationChanged = super.setIgnoreOrientationRequest(ignoreOrientationRequest);
mWmService.mDisplayWindowSettings.setIgnoreOrientationRequest(
this, mSetIgnoreOrientationRequest);
+ if (ignoreOrientationRequest && mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+ forAllActivities(r -> {
+ r.finishFixedRotationTransform();
+ });
+ }
return rotationChanged;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8272e16..a5da5e7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1239,7 +1239,6 @@
* @param lastRotation The most recently used rotation.
* @return The surface rotation to use.
*/
- @VisibleForTesting
@Surface.Rotation
int rotationForOrientation(@ScreenOrientation int orientation,
@Surface.Rotation int lastRotation) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 7a95c2d..2f0ee17 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -129,21 +129,33 @@
@WindowConfiguration.WindowingMode
private int getWindowingModeLocked(@NonNull SettingsProvider.SettingsEntry settings,
@NonNull DisplayContent dc) {
- int windowingMode = settings.mWindowingMode;
+ final int windowingModeFromDisplaySettings = settings.mWindowingMode;
// This display used to be in freeform, but we don't support freeform anymore, so fall
// back to fullscreen.
- if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
+ if (windowingModeFromDisplaySettings == WindowConfiguration.WINDOWING_MODE_FREEFORM
&& !mService.mAtmService.mSupportsFreeformWindowManagement) {
return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
}
- // No record is present so use default windowing mode policy.
- if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
- windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
- && (mService.mIsPc || dc.forceDesktopMode())
- ? WindowConfiguration.WINDOWING_MODE_FREEFORM
- : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ if (windowingModeFromDisplaySettings != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+ return windowingModeFromDisplaySettings;
}
- return windowingMode;
+ // No record is present so use default windowing mode policy.
+ final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
+ && (mService.mIsPc || dc.forceDesktopMode());
+ if (forceFreeForm) {
+ return WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ }
+ final int currentWindowingMode = dc.getDefaultTaskDisplayArea().getWindowingMode();
+ if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
+ // No record preset in settings + no mode set via the display area policy.
+ // Move to fullscreen as a fallback.
+ return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ }
+ if (currentWindowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
+ // Freeform was enabled before but disabled now, the TDA should now move to fullscreen.
+ return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+ }
+ return currentWindowingMode;
}
@WindowConfiguration.WindowingMode
diff --git a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java b/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
deleted file mode 100644
index e94f17c..0000000
--- a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.os.IBinder;
-import android.view.WindowManager;
-
-/**
- * Callback the IME targeting window visibility change state for
- * {@link com.android.server.inputmethod.InputMethodManagerService} to manage the IME surface
- * visibility and z-ordering.
- */
-public interface ImeTargetChangeListener {
- /**
- * Called when a non-IME-focusable overlay window being the IME layering target (e.g. a
- * window with {@link android.view.WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} and
- * {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flags)
- * has changed its window visibility.
- *
- * @param overlayWindowToken the window token of the overlay window.
- * @param windowType the window type of the overlay window.
- * @param visible the visibility of the overlay window, {@code true} means visible
- * and {@code false} otherwise.
- * @param removed Whether the IME target overlay window has being removed.
- * @param displayId display ID where the overlay window exists.
- */
- default void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType,
- boolean visible, boolean removed, int displayId) {
- }
-
- /**
- * Called when the visibility of IME input target window has changed.
- *
- * @param imeInputTarget the window token of the IME input target window.
- * @param visible the new window visibility made by {@code imeInputTarget}. visible is
- * {@code true} when switching to the new visible IME input target
- * window and started input, or the same input target relayout to
- * visible from invisible. In contrast, visible is {@code false} when
- * closing the input target, or the same input target relayout to
- * invisible from visible.
- * @param removed Whether the IME input target window has being removed.
- * @param displayId display ID where the overlay window exists.
- */
- default void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget, boolean visible,
- boolean removed, int displayId) {
- }
-}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 7a0fd3e..e18ca85 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -39,6 +39,7 @@
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static com.android.window.flags.Flags.reduceKeyguardTransitions;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -77,6 +78,8 @@
private static final int DEFER_WAKE_TRANSITION_TIMEOUT_MS = 5000;
+ private static final int GOING_AWAY_TIMEOUT_MS = 10500;
+
private final ActivityTaskSupervisor mTaskSupervisor;
private WindowManagerService mWindowManager;
@@ -232,6 +235,7 @@
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
}
+ scheduleGoingAwayTimeout(displayId);
}
// Update the sleep token first such that ensureActivitiesVisible has correct sleep token
@@ -286,6 +290,8 @@
mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.addStartingWindowsForVisibleActivities();
mWindowManager.executeAppTransition();
+
+ scheduleGoingAwayTimeout(displayId);
} finally {
mService.continueWindowLayout();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -417,31 +423,42 @@
final TransitionController tc = mRootWindowContainer.mTransitionController;
final KeyguardDisplayState state = getDisplayState(displayId);
+ final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId);
- final boolean occluded = state.mOccluded;
- final boolean performTransition = isKeyguardLocked(displayId);
- final boolean executeTransition = performTransition && !tc.isCollecting();
+ final boolean locked = isKeyguardLocked(displayId);
+ final boolean executeTransition = !tc.isShellTransitionsEnabled()
+ || (locked && !tc.isCollecting() && !reduceKeyguardTransitions());
- mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded);
+ final int transitType, transitFlags, notFlags;
+ if (state.mOccluded) {
+ transitType = TRANSIT_KEYGUARD_OCCLUDE;
+ transitFlags = TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+ notFlags = TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+ } else {
+ transitType = TRANSIT_KEYGUARD_UNOCCLUDE;
+ transitFlags = TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+ notFlags = TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+ }
+
+ mWindowManager.mPolicy.onKeyguardOccludedChangedLw(state.mOccluded);
mService.deferWindowLayout();
try {
- if (isKeyguardLocked(displayId)) {
- final int type = occluded ? TRANSIT_KEYGUARD_OCCLUDE : TRANSIT_KEYGUARD_UNOCCLUDE;
- final int flag = occluded ? TRANSIT_FLAG_KEYGUARD_OCCLUDING
- : TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+ if (locked) {
if (tc.isShellTransitionsEnabled()) {
- final Task trigger = (occluded && topActivity != null)
+ final Task trigger = (state.mOccluded && topActivity != null)
? topActivity.getRootTask() : null;
- Transition transition = tc.requestTransitionIfNeeded(type, flag, trigger,
- mRootWindowContainer.getDefaultDisplay());
+ tc.requestTransitionIfNeeded(transitType, transitFlags, trigger, dc);
+ final Transition transition = tc.getCollectingTransition();
+ if ((transition.getFlags() & notFlags) != 0 && reduceKeyguardTransitions()) {
+ transition.removeFlag(notFlags);
+ } else {
+ transition.addFlag(transitFlags);
+ }
if (trigger != null) {
- if (transition == null) {
- transition = tc.getCollectingTransition();
- }
transition.collect(trigger);
}
} else {
- mRootWindowContainer.getDefaultDisplay().prepareAppTransition(type, flag);
+ dc.prepareAppTransition(transitType, transitFlags);
}
} else {
if (tc.inTransition()) {
@@ -451,8 +468,8 @@
}
}
updateKeyguardSleepToken(displayId);
- if (performTransition && executeTransition) {
- mWindowManager.executeAppTransition();
+ if (executeTransition) {
+ dc.executeAppTransition();
}
} finally {
mService.continueWindowLayout();
@@ -590,6 +607,34 @@
}
}
+ /**
+ * Called when the default display's mKeyguardGoingAway has been left as {@code true} for too
+ * long. Send an explicit message to the KeyguardService asking it to wrap up.
+ */
+ private final Runnable mGoingAwayTimeout = () -> {
+ synchronized (mWindowManager.mGlobalLock) {
+ KeyguardDisplayState state = getDisplayState(DEFAULT_DISPLAY);
+ if (!state.mKeyguardGoingAway) {
+ return;
+ }
+ state.mKeyguardGoingAway = false;
+ state.writeEventLog("goingAwayTimeout");
+ mWindowManager.mPolicy.startKeyguardExitAnimation(0);
+ }
+ };
+
+ private void scheduleGoingAwayTimeout(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+ if (getDisplayState(displayId).mKeyguardGoingAway) {
+ if (!mWindowManager.mH.hasCallbacks(mGoingAwayTimeout)) {
+ mWindowManager.mH.postDelayed(mGoingAwayTimeout, GOING_AWAY_TIMEOUT_MS);
+ }
+ } else {
+ mWindowManager.mH.removeCallbacks(mGoingAwayTimeout);
+ }
+ }
/** Represents Keyguard state per individual display. */
private static class KeyguardDisplayState {
@@ -709,6 +754,7 @@
if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
writeEventLog("dismissIfInsecure");
controller.handleDismissInsecureKeyguard(display);
+ controller.scheduleGoingAwayTimeout(mDisplayId);
hasChange = true;
} else if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 38df1b0..4740fc4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -492,6 +492,9 @@
return;
}
+ pw.println(prefix + "isTransparentPolicyRunning="
+ + mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning());
+
boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed();
pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
if (!areBoundsLetterboxed) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 32ec020..7c875c1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -324,22 +324,6 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- @Override
- public boolean performHapticFeedback(int effectId, int flags, int privFlags) {
- final long ident = Binder.clearCallingIdentity();
- try {
- return mService.mPolicy.performHapticFeedback(mUid, mPackageName, effectId, null, flags,
- privFlags);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- @Override
- public void performHapticFeedbackAsync(int effectId, int flags, int privFlags) {
- performHapticFeedback(effectId, flags, privFlags);
- }
-
/* Drag/drop */
@Override
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 99e1e8b..0f9c001 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -19,8 +19,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -202,10 +204,12 @@
}
private static boolean isTransitionOpen(int type) {
- return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
+ return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT
+ || type == TRANSIT_PREPARE_BACK_NAVIGATION;
}
private static boolean isTransitionClose(int type) {
- return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
+ return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK
+ || type == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 16fcb09..1c8c245 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -313,6 +313,7 @@
proto.appearance = mSnapshot.getAppearance();
proto.isTranslucent = mSnapshot.isTranslucent();
proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
+ proto.uiMode = mSnapshot.getUiMode();
proto.id = mSnapshot.getId();
final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
final File file = mPersistInfoProvider.getProtoFile(mId, mUserId);
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index b7944d3..a83e8c7 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -116,7 +116,7 @@
final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(Display.DEFAULT_DISPLAY);
- final DisplayCutout displayCutout = display.getCutout();
+ final DisplayCutout displayCutout = display != null ? display.getCutout() : null;
if (displayCutout != null) {
// Expand swipe start threshold such that we can catch touches that just start beyond
// the notch area
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index eaf3012..dba1c36 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1079,8 +1079,11 @@
// Use launch-adjacent-flag-root if launching with launch-adjacent flag.
if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& mLaunchAdjacentFlagRootTask != null) {
- if (sourceTask != null && sourceTask == candidateTask) {
- // Do nothing when task that is getting opened is same as the source.
+ if (sourceTask != null && (sourceTask == candidateTask
+ || sourceTask.topRunningActivity() == null)) {
+ // Do nothing when task that is getting opened is same as the source or when
+ // the source is no-longer valid.
+ Slog.w(TAG_WM, "Ignoring LAUNCH_ADJACENT because adjacent source is gone.");
} else if (sourceTask != null
&& mLaunchAdjacentFlagRootTask.getAdjacentTask() != null
&& (sourceTask == mLaunchAdjacentFlagRootTask
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5698750..486a61b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -358,8 +358,12 @@
return mToken;
}
- void addFlag(int flag) {
- mFlags |= flag;
+ void addFlag(@TransitionFlags int flags) {
+ mFlags |= flags;
+ }
+
+ void removeFlag(@TransitionFlags int flags) {
+ mFlags &= ~flags;
}
void calcParallelCollectType(WindowContainerTransaction wct) {
@@ -3350,6 +3354,15 @@
return chg.hasChanged();
}
+ boolean hasChanges() {
+ for (int i = 0; i < mParticipants.size(); ++i) {
+ if (mChanges.get(mParticipants.valueAt(i)).hasChanged()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 2f46103..39b2635 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -92,6 +92,7 @@
if (parent == null) {
return;
}
+ final boolean wasStarted = mTransparentPolicyState.isRunning();
mTransparentPolicyState.reset();
// In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
// opaque activity constraints because we're expecting the activity is already letterboxed.
@@ -102,6 +103,9 @@
// We check if we need for some reason to skip the policy gievn the specific first
// opaque activity
if (shouldSkipTransparentPolicy(firstOpaqueActivity)) {
+ if (wasStarted) {
+ mActivityRecord.recomputeConfiguration();
+ }
return;
}
mTransparentPolicyState.start(firstOpaqueActivity);
@@ -190,7 +194,6 @@
// We skip letterboxing if the translucent activity doesn't have any
// opaque activities beneath or the activity below is embedded which
// never has letterbox.
- mActivityRecord.recomputeConfiguration();
return true;
}
if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
@@ -260,6 +263,10 @@
mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
mActivityRecord, mFirstOpaqueActivity,
(opaqueConfig, transparentOverrideConfig) -> {
+ if (!isPolicyEnabled()) {
+ transparentOverrideConfig.unset();
+ return transparentOverrideConfig;
+ }
resetTranslucentOverrideConfig(transparentOverrideConfig);
final Rect parentBounds = parent.getWindowConfiguration().getBounds();
final Rect bounds = transparentOverrideConfig
@@ -313,7 +320,17 @@
}
private boolean isRunning() {
- return mLetterboxConfigListener != null;
+ return mLetterboxConfigListener != null && isPolicyEnabled();
+ }
+
+ private boolean isPolicyEnabled() {
+ if (!mActivityRecord.mWmService.mFlags.mRespectNonTopVisibleFixedOrientation) {
+ return true;
+ }
+ // Do not enable the policy if the activity can affect display orientation.
+ final int orientation = mActivityRecord.getOverrideOrientation();
+ return orientation == SCREEN_ORIENTATION_UNSPECIFIED
+ || !mActivityRecord.handlesOrientationChangeFromDescendant(orientation);
}
private void clearInheritedCompatDisplayInsets() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index a574845..82d39a3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -990,16 +990,6 @@
}
/**
- * Sets by the {@link com.android.server.inputmethod.InputMethodManagerService} to monitor
- * the visibility change of the IME targeted windows.
- *
- * @see ImeTargetChangeListener#onImeTargetOverlayVisibilityChanged
- * @see ImeTargetChangeListener#onImeInputTargetVisibilityChanged
- */
- public abstract void setInputMethodTargetChangeListener(
- @NonNull ImeTargetChangeListener listener);
-
- /**
* Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}.
*/
public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cf92f1b..5749272 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -789,7 +789,6 @@
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
WindowManagerInternal.OnImeRequestedChangedListener mOnImeRequestedChangedListener;
- @Nullable ImeTargetChangeListener mImeTargetChangeListener;
SettingsObserver mSettingsObserver;
final EmbeddedWindowController mEmbeddedWindowController;
@@ -3517,29 +3516,28 @@
void dispatchImeTargetOverlayVisibilityChanged(@NonNull IBinder token,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed, int displayId) {
- if (mImeTargetChangeListener != null) {
- if (DEBUG_INPUT_METHOD) {
- Slog.d(TAG, "onImeTargetOverlayVisibilityChanged, win=" + mWindowMap.get(token)
- + ", type=" + ViewDebug.intToString(WindowManager.LayoutParams.class,
- "type", windowType) + "visible=" + visible + ", removed=" + removed
- + ", displayId=" + displayId);
- }
- mH.post(() -> mImeTargetChangeListener.onImeTargetOverlayVisibilityChanged(token,
- windowType, visible, removed, displayId));
+ if (DEBUG_INPUT_METHOD) {
+ Slog.d(TAG, "onImeTargetOverlayVisibilityChanged, win=" + mWindowMap.get(token)
+ + ", type=" + ViewDebug.intToString(WindowManager.LayoutParams.class,
+ "type", windowType) + "visible=" + visible + ", removed=" + removed
+ + ", displayId=" + displayId);
}
+ // Ignoring the starting window since it's ok to cover the IME target
+ // window in temporary without affecting the IME visibility.
+ final boolean hasOverlay = visible && !removed && windowType != TYPE_APPLICATION_STARTING;
+ mH.post(() -> InputMethodManagerInternal.get().setHasVisibleImeLayeringOverlay(hasOverlay,
+ displayId));
}
void dispatchImeInputTargetVisibilityChanged(@NonNull IBinder token, boolean visible,
boolean removed, int displayId) {
- if (mImeTargetChangeListener != null) {
- if (DEBUG_INPUT_METHOD) {
- Slog.d(TAG, "onImeInputTargetVisibilityChanged, win=" + mWindowMap.get(token)
- + "visible=" + visible + ", removed=" + removed
- + ", displayId" + displayId);
- }
- mH.post(() -> mImeTargetChangeListener.onImeInputTargetVisibilityChanged(token,
- visible, removed, displayId));
+ if (DEBUG_INPUT_METHOD) {
+ Slog.d(TAG, "onImeInputTargetVisibilityChanged, win=" + mWindowMap.get(token)
+ + "visible=" + visible + ", removed=" + removed + ", displayId=" + displayId);
}
+ final boolean visibleAndNotRemoved = visible && !removed;
+ mH.post(() -> InputMethodManagerInternal.get().onImeInputTargetVisibilityChanged(token,
+ visibleAndNotRemoved, displayId));
}
@Override
@@ -8675,13 +8673,6 @@
}
@Override
- public void setInputMethodTargetChangeListener(@NonNull ImeTargetChangeListener listener) {
- synchronized (mGlobalLock) {
- mImeTargetChangeListener = listener;
- }
- }
-
- @Override
public void setOrientationRequestPolicy(boolean respected,
int[] fromOrientations, int[] toOrientations) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0093e9d..58c48ad 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -59,6 +60,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
@@ -436,6 +438,11 @@
// the same transition instead of relying on this possible racing condition.
return;
}
+ if (transition.mType == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
+ && mService.mBackNavigationController.restoreBackNavigationSetTransitionReady(
+ transition)) {
+ return;
+ }
transition.setAllReady();
}
@@ -1386,6 +1393,12 @@
task.setTrimmableFromRecents(hop.isTrimmableFromRecents());
break;
}
+ case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: {
+ if (mService.mBackNavigationController.restoreBackNavigation()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ break;
+ }
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e646752..ec2fd3f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -55,6 +55,7 @@
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NOT_MAGNIFIABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -2989,6 +2990,25 @@
return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
}
+ @Override
+ void resolveOverrideConfiguration(Configuration newParentConfig) {
+ super.resolveOverrideConfiguration(newParentConfig);
+ if (mActivityRecord != null) {
+ // Let the activity decide whether to apply the size override.
+ return;
+ }
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ resolvedConfig.seq = newParentConfig.seq;
+ applySizeOverrideIfNeeded(
+ getDisplayContent(),
+ mSession.mProcess.mInfo,
+ newParentConfig,
+ resolvedConfig,
+ (mAttrs.privateFlags & PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE) != 0,
+ false /* hasFixedRotationTransform */,
+ false /* hasCompatDisplayInsets */);
+ }
+
/**
* @return {@code true} if this window can receive touches based on among other things,
* windowing state and recents animation state.
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2edf129..cf96114 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -934,7 +934,6 @@
}
// Hold the lock in order to manage the running list.
-// the listener.
void AnrTimerService::expire(timer_id_t timerId) {
// Save the timer attributes for the notification
int pid = 0;
@@ -967,7 +966,6 @@
// Deliver the notification outside of the lock.
if (expired) {
if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) {
- AutoMutex _l(lock_);
// Notification failed, which means the listener will never call accept() or
// discard(). Do not reinsert the timer.
discard(timerId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 669a999..a08af72 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -2080,10 +2080,14 @@
String tag = parser.getName();
switch (tag) {
case TAG_LOCAL_POLICY_ENTRY:
- readLocalPoliciesInner(parser);
+ int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
+ }
+ readPoliciesInner(parser, mLocalPolicies.get(userId));
break;
case TAG_GLOBAL_POLICY_ENTRY:
- readGlobalPoliciesInner(parser);
+ readPoliciesInner(parser, mGlobalPolicies);
break;
case TAG_ENFORCING_ADMINS_ENTRY:
readEnforcingAdminsInner(parser);
@@ -2100,64 +2104,45 @@
}
}
- private void readLocalPoliciesInner(TypedXmlPullParser parser)
- throws XmlPullParserException, IOException {
- int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
- PolicyKey policyKey = null;
- PolicyState<?> policyState = null;
- int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- String tag = parser.getName();
- switch (tag) {
- case TAG_POLICY_KEY_ENTRY:
- policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
- break;
- case TAG_POLICY_STATE_ENTRY:
- policyState = PolicyState.readFromXml(parser);
- break;
- default:
- Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag);
- }
- }
-
- if (policyKey != null && policyState != null) {
- if (!mLocalPolicies.contains(userId)) {
- mLocalPolicies.put(userId, new HashMap<>());
- }
- mLocalPolicies.get(userId).put(policyKey, policyState);
- } else {
- Slogf.wtf(TAG, "Error parsing local policy, policyKey is "
- + (policyKey == null ? "null" : policyKey) + ", and policyState is "
- + (policyState == null ? "null" : policyState) + ".");
- }
- }
-
- private void readGlobalPoliciesInner(TypedXmlPullParser parser)
+ private static void readPoliciesInner(
+ TypedXmlPullParser parser, Map<PolicyKey, PolicyState<?>> policyStateMap)
throws IOException, XmlPullParserException {
PolicyKey policyKey = null;
+ PolicyDefinition<?> policyDefinition = null;
PolicyState<?> policyState = null;
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
switch (tag) {
case TAG_POLICY_KEY_ENTRY:
- policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ if (Flags.dontReadPolicyDefinition()) {
+ policyDefinition = PolicyDefinition.readFromXml(parser);
+ if (policyDefinition != null) {
+ policyKey = policyDefinition.getPolicyKey();
+ }
+ } else {
+ policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ }
break;
case TAG_POLICY_STATE_ENTRY:
- policyState = PolicyState.readFromXml(parser);
+ if (Flags.dontReadPolicyDefinition() && policyDefinition == null) {
+ Slogf.w(TAG, "Skipping policy state - unknown policy definition");
+ } else {
+ policyState = PolicyState.readFromXml(policyDefinition, parser);
+ }
break;
default:
- Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag);
+ Slogf.wtf(TAG, "Unknown tag for policy entry" + tag);
}
}
- if (policyKey != null && policyState != null) {
- mGlobalPolicies.put(policyKey, policyState);
- } else {
- Slogf.wtf(TAG, "Error parsing global policy, policyKey is "
- + (policyKey == null ? "null" : policyKey) + ", and policyState is "
- + (policyState == null ? "null" : policyState) + ".");
+ if (policyKey == null || policyState == null) {
+ Slogf.wtf(TAG, "Error parsing policy, policyKey is %s, and policyState is %s.",
+ policyKey, policyState);
+ return;
}
+
+ policyStateMap.put(policyKey, policyState);
}
private void readEnforcingAdminsInner(TypedXmlPullParser parser)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8e3248e..f86d307 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -708,17 +708,15 @@
}
@Nullable
- static <V> PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
+ static PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- // TODO: can we avoid casting?
PolicyKey policyKey = PolicyKey.readGenericPolicyKeyFromXml(parser);
if (policyKey == null) {
Slogf.wtf(TAG, "Error parsing PolicyKey, GenericPolicyKey is null");
return null;
}
- PolicyDefinition<PolicyValue<V>> genericPolicyDefinition =
- (PolicyDefinition<PolicyValue<V>>) POLICY_DEFINITIONS.get(
- policyKey.getIdentifier());
+ PolicyDefinition<?> genericPolicyDefinition =
+ POLICY_DEFINITIONS.get(policyKey.getIdentifier());
if (genericPolicyDefinition == null) {
Slogf.wtf(TAG, "Error parsing PolicyKey, Unknown generic policy key: " + policyKey);
return null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 245c438..b813489 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
+import android.app.admin.flags.Flags;
import android.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -254,11 +255,9 @@
}
@Nullable
- static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
+ static <V> PolicyState<V> readFromXml(
+ PolicyDefinition<V> policyDefinition, TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
-
- PolicyDefinition<V> policyDefinition = null;
-
PolicyValue<V> currentResolvedPolicy = null;
LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
@@ -300,10 +299,15 @@
}
break;
case TAG_POLICY_DEFINITION_ENTRY:
- policyDefinition = PolicyDefinition.readFromXml(parser);
- if (policyDefinition == null) {
- Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
- + "PolicyDefinition is null");
+ if (Flags.dontReadPolicyDefinition()) {
+ // Should be passed by the caller.
+ Objects.requireNonNull(policyDefinition);
+ } else {
+ policyDefinition = PolicyDefinition.readFromXml(parser);
+ if (policyDefinition == null) {
+ Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, "
+ + "PolicyDefinition is null");
+ }
}
break;
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 8ae4f9a..6afcae7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -45,7 +45,11 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.Display;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -58,6 +62,7 @@
import com.android.internal.inputmethod.StartInputReason;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,6 +75,9 @@
*/
@RunWith(AndroidJUnit4.class)
public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
private DefaultImeVisibilityApplier mVisibilityApplier;
@Before
@@ -112,6 +120,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testApplyImeVisibility_showIme() {
final var statsToken = ImeTracker.Token.empty();
synchronized (ImfLock.class) {
@@ -122,6 +131,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testApplyImeVisibility_hideIme() {
final var statsToken = ImeTracker.Token.empty();
synchronized (ImfLock.class) {
@@ -141,7 +151,12 @@
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
- verifyHideSoftInput(true, true);
+ if (Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, true /* invoked */);
+ } else {
+ verifyHideSoftInput(true, true);
+ }
}
@Test
@@ -153,7 +168,12 @@
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
- verifyHideSoftInput(true, true);
+ if (Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, true /* invoked */);
+ } else {
+ verifyHideSoftInput(true, true);
+ }
}
@Test
@@ -162,10 +182,16 @@
mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(),
STATE_SHOW_IME_IMPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId);
}
- verifyShowSoftInput(true, true, 0 /* showFlags */);
+ if (Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, true /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(true, true, 0 /* showFlags */);
+ }
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
// Init a IME target client on the secondary display to show IME.
mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
@@ -234,8 +260,10 @@
verify(mVisibilityApplier).applyImeVisibility(
eq(mWindowToken), any(), eq(STATE_HIDE_IME),
eq(SoftInputShowHideReason.NOT_SET), eq(mUserId) /* userId */);
- verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
- eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
+ if (!Flags.refactorInsetsController()) {
+ verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(eq(mWindowToken),
+ eq(displayIdToShowIme), and(not(eq(statsToken)), notNull()));
+ }
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index dc03732..aee7242 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -20,6 +20,7 @@
import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_RECENT;
import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_STATIC;
import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.SwitchMode;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -61,7 +62,6 @@
private static final boolean TEST_IS_VR_IME = false;
private static final int TEST_IS_DEFAULT_RES_ID = 0;
private static final String SYSTEM_LOCALE = "en_US";
- private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -103,7 +103,7 @@
TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
if (subtypes == null) {
items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
- NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
+ NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
} else {
for (int i = 0; i < subtypes.size(); ++i) {
final String subtypeLocale = subtypeLocales.get(i);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index d7af443..c272430 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -924,6 +924,54 @@
}
@Test
+ public void testSameVersions_writeReadUsesStaticLibraries() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+ final String libOne = "one";
+ final String libTwo = "two";
+ final long versionOne = 311;
+ packageSetting.setUsesStaticLibraries(new String[] { libOne, libTwo });
+ packageSetting.setUsesStaticLibrariesVersions(new long[] { versionOne, versionOne });
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+ assertThat(resultSetting.getUsesStaticLibraries()[0], is(libOne));
+ assertThat(resultSetting.getUsesStaticLibraries()[1], is(libTwo));
+ assertThat(resultSetting.getUsesStaticLibrariesVersions()[0], is(versionOne));
+ assertThat(resultSetting.getUsesStaticLibrariesVersions()[1], is(versionOne));
+ }
+
+ @Test
+ public void testSameLibNames_writeReadUsesStaticLibraries() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+ final String libOne = "one";
+ final long versionOne = 311;
+ final long versionTwo = 330;
+ packageSetting.setUsesStaticLibraries(new String[] { libOne, libOne});
+ packageSetting.setUsesStaticLibrariesVersions(new long[] { versionOne, versionTwo });
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+ assertThat(resultSetting.getUsesStaticLibraries().length, is(1));
+ assertThat(resultSetting.getUsesStaticLibrariesVersions().length, is(1));
+ assertThat(resultSetting.getUsesStaticLibraries()[0], is(libOne));
+ assertThat(resultSetting.getUsesStaticLibrariesVersions()[0], is(versionTwo));
+ }
+
+ @Test
public void testWriteReadUsesSdkLibraries() {
final Settings settingsUnderTest = makeSettings();
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -1008,6 +1056,65 @@
}
@Test
+ public void testSameVersions_writeReadUsesSdkLibraries() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+ final String libOne = "one";
+ final String libTwo = "two";
+ final long versionOne = 311;
+ final boolean optional = false;
+ packageSetting.setUsesSdkLibraries(new String[] { libOne, libTwo });
+ packageSetting.setUsesSdkLibrariesVersionsMajor(new long[] { versionOne, versionOne });
+ packageSetting.setUsesSdkLibrariesOptional(new boolean[] { optional, optional });
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+
+ assertThat(resultSetting.getUsesSdkLibraries()[0], is(libOne));
+ assertThat(resultSetting.getUsesSdkLibraries()[1], is(libTwo));
+ assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[0], is(versionOne));
+ assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[1], is(versionOne));
+ assertThat(resultSetting.getUsesSdkLibrariesOptional()[0], is(optional));
+ assertThat(resultSetting.getUsesSdkLibrariesOptional()[1], is(optional));
+ }
+
+ @Test
+ public void testSameLibNames_writeReadUsesSdkLibraries() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+
+ final String libOne = "one";
+ final long versionOne = 311;
+ final long versionTwo = 330;
+ final boolean optionalOne = false;
+ final boolean optionalTwo = true;
+ packageSetting.setUsesSdkLibraries(new String[] { libOne, libOne });
+ packageSetting.setUsesSdkLibrariesVersionsMajor(new long[] { versionOne, versionTwo });
+ packageSetting.setUsesSdkLibrariesOptional(new boolean[] { optionalOne, optionalTwo });
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ PackageSetting resultSetting = settings.getPackageLPr(PACKAGE_NAME_1);
+
+ assertThat(resultSetting.getUsesSdkLibraries().length, is(1));
+ assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor().length, is(1));
+ assertThat(resultSetting.getUsesSdkLibrariesOptional().length, is(1));
+ assertThat(resultSetting.getUsesSdkLibraries()[0], is(libOne));
+ assertThat(resultSetting.getUsesSdkLibrariesVersionsMajor()[0], is(versionTwo));
+ assertThat(resultSetting.getUsesSdkLibrariesOptional()[0], is(optionalTwo));
+ }
+
+ @Test
public void testWriteReadPendingRestore() {
Settings settings = makeSettings();
PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index 0def516..8753b25 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -45,7 +45,6 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-import java.util.concurrent.TimeUnit
import java.util.LinkedList
import java.util.Queue
import android.util.ArraySet
@@ -117,9 +116,6 @@
Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
- // VirtualMouse is created on a separate thread.
- // Wait for VirtualMouse to be created before running tests
- TimeUnit.MILLISECONDS.sleep(20L)
mouseKeysInterceptor.next = nextInterceptor
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index f98bbf9..2b93ccb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -2120,6 +2120,34 @@
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveOsdName);
}
+ @Test
+ public void onOneTouchPlay_wakeUp_addCecDevice() {
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .isEmpty();
+ mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
+ HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED);
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage textViewOn = HdmiCecMessageBuilder.buildTextViewOn(ADDR_PLAYBACK_1,
+ mTvLogicalAddress);
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1,
+ 0x1000);
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(textViewOn)).isEqualTo(Constants.HANDLED);
+ assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(activeSource)).isEqualTo(
+ Constants.HANDLED);
+ mTestLooper.dispatchAll();
+ assertThat(mPowerManager.isInteractive()).isTrue();
+
+ // FakePowerManagerWrapper#wakeUp() doesn't broadcast Intent.ACTION_SCREEN_ON so we have to
+ // manually call this method.
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
+ .hasSize(1);
+ }
+
protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
MockTvDevice(HdmiControlService service) {
super(service);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 473d1dc..1074f7b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -240,6 +240,9 @@
public void isValid_setAnalogueTimer_clearAnalogueTimer() {
assertMessageValidity("04:33:0C:08:10:1E:04:30:08:00:13:AD:06").isEqualTo(OK);
assertMessageValidity("04:34:04:0C:16:0F:08:37:00:02:EA:60:03:34").isEqualTo(OK);
+ // Allow [Recording Sequence] set multiple days of the week.
+ // e.g. Monday (0x02) | Tuesday (0x04) -> Monday or Tuesday (0x06)
+ assertMessageValidity("04:34:04:0C:16:0F:08:37:06:02:EA:60:03:34").isEqualTo(OK);
assertMessageValidity("0F:33:0C:08:10:1E:04:30:08:00:13:AD:06")
.isEqualTo(ERROR_DESTINATION);
@@ -308,7 +311,7 @@
// Invalid Recording Sequence
assertMessageValidity("04:99:12:06:0C:2D:5A:19:90:91:04:00:B1").isEqualTo(ERROR_PARAMETER);
// Invalid Recording Sequence
- assertMessageValidity("04:97:0C:08:15:05:04:1E:21:00:C4:C2:11:D8:75:30")
+ assertMessageValidity("04:97:0C:08:15:05:04:1E:A1:00:C4:C2:11:D8:75:30")
.isEqualTo(ERROR_PARAMETER);
// Invalid Digital Broadcast System
@@ -354,7 +357,7 @@
// Invalid Recording Sequence
assertMessageValidity("40:A2:14:09:12:28:4B:19:84:05:10:00").isEqualTo(ERROR_PARAMETER);
// Invalid Recording Sequence
- assertMessageValidity("40:A1:0C:08:15:05:04:1E:14:04:20").isEqualTo(ERROR_PARAMETER);
+ assertMessageValidity("40:A1:0C:08:15:05:04:1E:94:04:20").isEqualTo(ERROR_PARAMETER);
// Invalid external source specifier
assertMessageValidity("40:A2:14:09:12:28:4B:19:10:08:10:00").isEqualTo(ERROR_PARAMETER);
// Invalid External PLug
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 3d68849..dddab65 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -108,6 +108,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -165,6 +166,7 @@
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SimpleClock;
import android.os.SystemClock;
@@ -197,6 +199,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
import com.android.internal.util.test.FsUtil;
@@ -2310,6 +2313,70 @@
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
+ @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS)
+ public void testRulesNeverAppliedToCoreUids() throws Exception {
+ clearInvocations(mNetworkManager);
+
+ final int coreAppId = Process.FIRST_APPLICATION_UID - 102;
+ final int coreUid = UserHandle.getUid(USER_ID, coreAppId);
+
+ // Enable all restrictions and add this core uid to all allowlists.
+ mService.mDeviceIdleMode = true;
+ mService.mRestrictPower = true;
+ setRestrictBackground(true);
+ expectHasUseRestrictedNetworksPermission(coreUid, true);
+ enableRestrictedMode(true);
+ final NetworkPolicyManagerInternal internal = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
+ internal.setLowPowerStandbyActive(true);
+ internal.setLowPowerStandbyAllowlist(new int[]{coreUid});
+ internal.onTempPowerSaveWhitelistChange(coreAppId, true, REASON_OTHER, "testing");
+
+ when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+ .thenReturn(new int[]{coreAppId});
+ mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+ // A normal uid would undergo a rule change from denied to allowed on all chains, but we
+ // should not request any rule change for this core uid.
+ verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(coreUid), anyInt());
+ verify(mNetworkManager, never()).setFirewallUidRules(anyInt(),
+ argThat(ar -> ArrayUtils.contains(ar, coreUid)), any(int[].class));
+ }
+
+ @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS)
+ public void testRulesNeverAppliedToUidsWithoutInternetPermission() throws Exception {
+ clearInvocations(mNetworkManager);
+
+ mService.mInternetPermissionMap.clear();
+ expectHasInternetPermission(UID_A, false);
+
+ // Enable all restrictions and add this uid to all allowlists.
+ mService.mDeviceIdleMode = true;
+ mService.mRestrictPower = true;
+ setRestrictBackground(true);
+ expectHasUseRestrictedNetworksPermission(UID_A, true);
+ enableRestrictedMode(true);
+ final NetworkPolicyManagerInternal internal = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
+ internal.setLowPowerStandbyActive(true);
+ internal.setLowPowerStandbyAllowlist(new int[]{UID_A});
+ internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+ when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+ .thenReturn(new int[]{APP_ID_A});
+ mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+ // A normal uid would undergo a rule change from denied to allowed on all chains, but we
+ // should not request any rule this uid without the INTERNET permission.
+ verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(UID_A), anyInt());
+ verify(mNetworkManager, never()).setFirewallUidRules(anyInt(),
+ argThat(ar -> ArrayUtils.contains(ar, UID_A)), any(int[].class));
+ }
+
private boolean isUidState(int uid, int procState, int procStateSeq, int capability) {
final NetworkPolicyManager.UidState uidState = mService.getUidStateForTest(uid);
if (uidState == null) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index d714db99..7912156 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -121,6 +121,9 @@
// Making a copy of mUsersToRemove to avoid ConcurrentModificationException
mUsersToRemove.stream().toList().forEach(this::removeUser);
mUserRemovalWaiter.close();
+
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ mContext.getUser());
}
private void removeExistingUsers() {
@@ -935,6 +938,35 @@
@MediumTest
@Test
+ @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY)
+ public void testSetUserAdminThrowsSecurityException() throws Exception {
+ UserInfo targetUser = createUser("SecondaryUser", /*flags=*/ 0);
+ assertThat(targetUser.isAdmin()).isFalse();
+
+ try {
+ // 1. Target User Restriction
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+ targetUser.getUserHandle());
+ assertThrows(SecurityException.class, () -> mUserManager.setUserAdmin(targetUser.id));
+
+ // 2. Current User Restriction
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ targetUser.getUserHandle());
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+ mContext.getUser());
+ assertThrows(SecurityException.class, () -> mUserManager.setUserAdmin(targetUser.id));
+
+ } finally {
+ // Ensure restriction is removed even if test fails
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ targetUser.getUserHandle());
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ mContext.getUser());
+ }
+ }
+
+ @MediumTest
+ @Test
public void testRevokeUserAdmin() throws Exception {
UserInfo userInfo = createUser("Admin", /*flags=*/ UserInfo.FLAG_ADMIN);
assertThat(userInfo.isAdmin()).isTrue();
@@ -959,6 +991,37 @@
@MediumTest
@Test
+ @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY)
+ public void testRevokeUserAdminThrowsSecurityException() throws Exception {
+ UserInfo targetUser = createUser("SecondaryUser", /*flags=*/ 0);
+ assertThat(targetUser.isAdmin()).isFalse();
+
+ try {
+ // 1. Target User Restriction
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+ targetUser.getUserHandle());
+ assertThrows(SecurityException.class, () -> mUserManager
+ .revokeUserAdmin(targetUser.id));
+
+ // 2. Current User Restriction
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ targetUser.getUserHandle());
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true,
+ mContext.getUser());
+ assertThrows(SecurityException.class, () -> mUserManager
+ .revokeUserAdmin(targetUser.id));
+
+ } finally {
+ // Ensure restriction is removed even if test fails
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ targetUser.getUserHandle());
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, false,
+ mContext.getUser());
+ }
+ }
+
+ @MediumTest
+ @Test
public void testGetProfileParent() throws Exception {
assumeManagedUsersSupported();
int mainUserId = mUserManager.getMainUser().getIdentifier();
@@ -1184,6 +1247,23 @@
}
}
+ // Make sure createUser for ADMIN would fail if we have DISALLOW_GRANT_ADMIN.
+ @MediumTest
+ @Test
+ @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_UNICORN_MODE_REFACTORING_FOR_HSUM_READ_ONLY)
+ public void testCreateAdminUser_disallowGrantAdmin() throws Exception {
+ final int creatorId = ActivityManager.getCurrentUser();
+ final UserHandle creatorHandle = asHandle(creatorId);
+ mUserManager.setUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, true, creatorHandle);
+ try {
+ UserInfo createdInfo = createUser("SecondaryUser", /*flags=*/ UserInfo.FLAG_ADMIN);
+ assertThat(createdInfo).isNull();
+ } finally {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false,
+ creatorHandle);
+ }
+ }
+
// Make sure createProfile would fail if we have DISALLOW_ADD_CLONE_PROFILE.
@MediumTest
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 4bea95f..7933f7a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -18,8 +18,8 @@
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static com.google.common.truth.Truth.assertThat;
@@ -48,7 +48,6 @@
import android.os.PowerManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenModeConfig;
import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
@@ -78,27 +77,6 @@
@Mock UiModeManager mUiModeManager;
@Mock WallpaperManager mWallpaperManager;
- private enum ChangeOrigin {
- ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN),
- ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT),
- ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER),
- ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER),
- ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP),
- ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI),
- ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP);
-
- private final int mValue;
-
- ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) {
- mValue = value;
- }
-
- @ZenModeConfig.ConfigChangeOrigin
- public int value() {
- return mValue;
- }
- }
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -123,7 +101,7 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(effects, UPDATE_ORIGIN_USER);
+ mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
@@ -141,14 +119,14 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+ mApplier.apply(previousEffects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
- mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+ mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
verify(mColorDisplayManager).setSaturationLevel(eq(100));
@@ -163,11 +141,11 @@
ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.build();
- mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+ mApplier.apply(previousEffects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
- mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+ mApplier.apply(noEffects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
@@ -186,7 +164,7 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(effects, UPDATE_ORIGIN_USER);
+ mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
// (And no crash from missing services).
@@ -207,7 +185,7 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(effects, UPDATE_ORIGIN_USER);
+ mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
verifyNoMoreInteractions(mWallpaperManager);
}
@@ -220,7 +198,7 @@
.setShouldDimWallpaper(true)
.setShouldDisplayGrayscale(true)
.build();
- mApplier.apply(effects, UPDATE_ORIGIN_USER);
+ mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
@@ -234,12 +212,12 @@
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(),
- UPDATE_ORIGIN_USER);
+ ORIGIN_USER_IN_SYSTEMUI);
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
// Apply a second effect and remove the first one.
mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(),
- UPDATE_ORIGIN_USER);
+ ORIGIN_USER_IN_SYSTEMUI);
// Wallpaper dimming was undone, Grayscale was applied, nothing else was touched.
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
@@ -259,7 +237,7 @@
when(mPowerManager.isInteractive()).thenReturn(true);
mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
- UPDATE_ORIGIN_APP);
+ ORIGIN_APP);
// Effect was not yet applied, but a broadcast receiver was registered.
verifyZeroInteractions(mUiModeManager);
@@ -278,7 +256,7 @@
@Test
public void apply_nightModeWithScreenOff_appliedImmediately(
- @TestParameter ChangeOrigin origin) {
+ @TestParameter ZenChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
when(mPowerManager.isInteractive()).thenReturn(false);
@@ -292,9 +270,10 @@
}
@Test
- @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}",
- "{origin: ORIGIN_INIT_USER}"})
- public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) {
+ @TestParameters({"{origin: ORIGIN_USER_IN_SYSTEMUI}", "{origin: ORIGIN_USER_IN_APP}",
+ "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}"})
+ public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(
+ ZenChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -309,8 +288,9 @@
@Test
@TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}",
- "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}", "{origin: ORIGIN_UNKNOWN}"})
- public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) {
+ "{origin: ORIGIN_SYSTEM}", "{origin: ORIGIN_UNKNOWN}"})
+ public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(
+ ZenChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -342,7 +322,7 @@
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
.build();
- mApplier.apply(effects, UPDATE_ORIGIN_USER);
+ mApplier.apply(effects, ORIGIN_USER_IN_SYSTEMUI);
// No crashes
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index de70280..14ad15e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -15,7 +15,9 @@
*/
package com.android.server.notification;
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
import static android.app.Notification.GROUP_ALERT_ALL;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
@@ -539,6 +541,36 @@
return r;
}
+ private NotificationRecord getAutogroupSummaryNotificationRecord(int id, String groupKey,
+ int groupAlertBehavior, UserHandle userHandle, String packageName) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH)
+ .setFlag(FLAG_GROUP_SUMMARY | FLAG_AUTOGROUP_SUMMARY, true);
+
+ int defaults = 0;
+ defaults |= Notification.DEFAULT_SOUND;
+ mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+ Notification.AUDIO_ATTRIBUTES_DEFAULT);
+
+ builder.setDefaults(defaults);
+ builder.setGroup(groupKey);
+ builder.setGroupAlertBehavior(groupAlertBehavior);
+ Notification n = builder.build();
+
+ Context context = spy(getContext());
+ PackageManager packageManager = spy(context.getPackageManager());
+ when(context.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)).thenReturn(false);
+
+ StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag,
+ mUid, mPid, n, userHandle, null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
+ mService.addNotification(r);
+ return r;
+ }
+
//
// Convenience functions for interacting with mocks
//
@@ -2603,6 +2635,79 @@
}
@Test
+ public void testBeepVolume_politeNotif_justSummaries() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+
+ // first update at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep at 50% volume
+ summary = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+ Mockito.reset(mRingtonePlayer);
+
+ // next update at 0% volume
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.0f);
+
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_autogroupSummary() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // child should beep at 100% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // summary 0% volume (GROUP_ALERT_CHILDREN)
+ NotificationRecord summary = getAutogroupSummaryNotificationRecord(mId, "a",
+ GROUP_ALERT_CHILDREN, mUser, mPkg);
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // next update at 50% volume because autogroup summary was ignored
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 1b999e4..5a8de58 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -10064,7 +10064,7 @@
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
- eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@Test
@@ -10086,7 +10086,7 @@
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
- eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@Test
@@ -10106,7 +10106,7 @@
// verify that zen mode helper gets passed in the package name from the arg, not the owner
verify(mockZenModeHelper).addAutomaticZenRule(
- eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP),
+ eq("another.package"), eq(rule), eq(ZenModeConfig.ORIGIN_APP),
anyString(), anyInt()); // doesn't count as a system/systemui call
}
@@ -10221,7 +10221,7 @@
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
- eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@Test
@@ -10233,7 +10233,7 @@
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
- eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), anyInt());
}
@Test
@@ -10245,7 +10245,7 @@
mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_APP), anyString(), anyInt());
}
@Test
@@ -10267,7 +10267,7 @@
mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
- eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@Test
@@ -10289,7 +10289,7 @@
mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
- eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyString(), anyInt());
}
@Test
@@ -10304,7 +10304,8 @@
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
+ public void setAutomaticZenRuleState_fromAppWithConditionFromUser_originUserInApp()
+ throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -10313,12 +10314,12 @@
mBinderService.setAutomaticZenRuleState("id", withSourceUser);
verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
- eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
+ eq(ZenModeConfig.ORIGIN_USER_IN_APP), anyInt());
}
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
+ public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_originApp()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
@@ -10328,12 +10329,26 @@
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+ eq(ZenModeConfig.ORIGIN_APP), anyInt());
}
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+ public void setAutomaticZenRuleState_fromSystemWithConditionFromUser_originUserInSystemUi()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI), anyInt());
+ }
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_originSystem()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.isSystemUid = true;
@@ -10343,7 +10358,7 @@
mBinderService.setAutomaticZenRuleState("id", withSourceContext);
verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
- eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
+ eq(ZenModeConfig.ORIGIN_SYSTEM), anyInt());
}
/** Prepares for a zen-related test that uses a mocked {@link ZenModeHelper}. */
@@ -13049,6 +13064,100 @@
@Test
@DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testCancelAutogroupSummary_cancelsAllChildren() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+ final int summaryId = Integer.MAX_VALUE;
+ // Add 2 group notifications without a summary
+ NotificationRecord nr0 =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+ NotificationRecord nr1 =
+ generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
+ mService.addNotification(nr0);
+ mService.addNotification(nr1);
+ mService.mSummaryByGroupKey.remove(nr0.getGroupKey());
+
+ // GroupHelper is a mock, so make the calls it would make
+ // Add aggregate group summary
+ NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
+ mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN,
+ nr0.getChannel().getId());
+ NotificationRecord aggregateSummary = mService.createAutoGroupSummary(nr0.getUserId(),
+ nr0.getSbn().getPackageName(), nr0.getKey(), aggregateGroupName, summaryId, attr);
+ mService.addNotification(aggregateSummary);
+ nr0.setOverrideGroupKey(aggregateGroupName);
+ nr1.setOverrideGroupKey(aggregateGroupName);
+ final String fullAggregateGroupKey = nr0.getGroupKey();
+
+ // Check that the aggregate group summary was created
+ assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
+ assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
+ nr0.getChannel().getId());
+ assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
+
+ // Cancel aggregate group summary
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, aggregateSummary.getSbn().getTag(),
+ aggregateSummary.getSbn().getId(), aggregateSummary.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that child notifications are also removed
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1));
+
+ // Make sure the summary was removed and not re-posted
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void testCancelAutogroupSummary_forceGrouping_cancelsAllChildren() throws Exception {
+ final String originalGroupName = "originalGroup";
+ final String aggregateGroupName = "Aggregate_Test";
+ final int summaryId = Integer.MAX_VALUE;
+ // Add 2 group notifications without a summary
+ NotificationRecord nr0 =
+ generateNotificationRecord(mTestNotificationChannel, 0, originalGroupName, false);
+ NotificationRecord nr1 =
+ generateNotificationRecord(mTestNotificationChannel, 1, originalGroupName, false);
+ mService.addNotification(nr0);
+ mService.addNotification(nr1);
+ mService.mSummaryByGroupKey.remove(nr0.getGroupKey());
+
+ // GroupHelper is a mock, so make the calls it would make
+ // Add aggregate group summary
+ NotificationAttributes attr = new NotificationAttributes(GroupHelper.BASE_FLAGS,
+ mock(Icon.class), 0, VISIBILITY_PRIVATE, GROUP_ALERT_CHILDREN,
+ nr0.getChannel().getId());
+ NotificationRecord aggregateSummary = mService.createAutoGroupSummary(nr0.getUserId(),
+ nr0.getSbn().getPackageName(), nr0.getKey(), aggregateGroupName, summaryId, attr);
+ mService.addNotification(aggregateSummary);
+ nr0.setOverrideGroupKey(aggregateGroupName);
+ nr1.setOverrideGroupKey(aggregateGroupName);
+ final String fullAggregateGroupKey = nr0.getGroupKey();
+
+ // Check that the aggregate group summary was created
+ assertThat(aggregateSummary.getNotification().getGroup()).isEqualTo(aggregateGroupName);
+ assertThat(aggregateSummary.getNotification().getChannelId()).isEqualTo(
+ nr0.getChannel().getId());
+ assertThat(mService.mSummaryByGroupKey.containsKey(fullAggregateGroupKey)).isTrue();
+
+ // Cancel aggregate group summary
+ mBinderService.cancelNotificationWithTag(mPkg, mPkg, aggregateSummary.getSbn().getTag(),
+ aggregateSummary.getSbn().getId(), aggregateSummary.getSbn().getUserId());
+ waitForIdle();
+
+ // Check that child notifications are also removed
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+
+ // Make sure the summary was removed and not re-posted
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
+ }
+
+ @Test
+ @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testUngroupingOngoingAutoSummary() throws Exception {
NotificationRecord nr0 =
generateNotificationRecord(mTestNotificationChannel, 0);
@@ -15508,7 +15617,7 @@
mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
+ eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(), eq("package"),
anyInt());
}
@@ -15554,7 +15663,7 @@
if (canSetGlobalPolicy) {
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+ eq(ZenModeConfig.ORIGIN_APP), anyString(), eq("package"), anyInt());
} else {
verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
@@ -15594,7 +15703,7 @@
INTERRUPTION_FILTER_PRIORITY);
verify(mService.mZenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
- eq(null), eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(),
+ eq(null), eq(ZenModeConfig.ORIGIN_SYSTEM), anyString(),
eq("pkg"), eq(mUid));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenChangeOrigin.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenChangeOrigin.java
new file mode 100644
index 0000000..5728788
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenChangeOrigin.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.notification;
+
+import android.service.notification.ZenModeConfig;
+
+/** Enum version of {@link ZenModeConfig.ConfigOrigin}, for test parameterization. */
+public enum ZenChangeOrigin {
+ ORIGIN_UNKNOWN(ZenModeConfig.ORIGIN_UNKNOWN),
+ ORIGIN_INIT(ZenModeConfig.ORIGIN_INIT),
+ ORIGIN_INIT_USER(ZenModeConfig.ORIGIN_INIT_USER),
+ ORIGIN_APP(ZenModeConfig.ORIGIN_APP),
+ ORIGIN_USER_IN_APP(ZenModeConfig.ORIGIN_USER_IN_APP),
+ ORIGIN_SYSTEM(ZenModeConfig.ORIGIN_SYSTEM),
+ ORIGIN_USER_IN_SYSTEMUI(ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI),
+ ORIGIN_RESTORE_BACKUP(ZenModeConfig.ORIGIN_RESTORE_BACKUP);
+
+ private final int mValue;
+
+ ZenChangeOrigin(@ZenModeConfig.ConfigOrigin int value) {
+ mValue = value;
+ }
+
+ /** Gets the {@link ZenModeConfig.ConfigOrigin} int value corresponding to the enum. */
+ @ZenModeConfig.ConfigOrigin
+ public int value() {
+ return mValue;
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
index 8add2f9..cdceb1f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -20,12 +20,14 @@
import android.app.AutomaticZenRule;
import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChangeOrigin;
import com.android.os.dnd.ChannelPolicy;
import com.android.os.dnd.ConversationType;
import com.android.os.dnd.PeopleType;
@@ -78,6 +80,11 @@
testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
}
+ @Test
+ public void testEnum_changeOrigin() {
+ testEnum(ZenModeConfig.class, "ORIGIN", ChangeOrigin.class, "ORIGIN");
+ }
+
/**
* Verifies that any constants (i.e. {@code public static final int} fields) named {@code
* <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 3c3c2f3..60c4ac7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -31,6 +31,8 @@
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.service.notification.ZenModeConfig.XML_VERSION_MODES_API;
+import static android.service.notification.ZenModeConfig.ZEN_TAG;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
@@ -283,8 +285,8 @@
// the default value from the zen mode config.
Policy policy = config.toNotificationPolicy(zenPolicy);
assertEquals(Flags.modesUi()
- ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
- : config.isAllowPriorityChannels(),
+ ? config.manualRule.zenPolicy.getPriorityChannelsAllowed() == STATE_ALLOW
+ : config.isAllowPriorityChannels(),
policy.allowPriorityChannels());
}
@@ -535,7 +537,7 @@
rule.triggerDescription = TRIGGER_DESC;
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
if (Flags.modesUi()) {
- rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER;
+ rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
}
Parcel parcel = Parcel.obtain();
@@ -650,7 +652,7 @@
rule.triggerDescription = TRIGGER_DESC;
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
if (Flags.modesUi()) {
- rule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP;
+ rule.disabledOrigin = ZenModeConfig.ORIGIN_APP;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -991,6 +993,58 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testConfigXml_manualRule_upgradeWhenExisting() throws Exception {
+ // prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
+ // data on it because it's meant to indicate that the manual rule is on by merely existing.
+ ZenModeConfig config = new ZenModeConfig();
+ config.manualRule = new ZenModeConfig.ZenRule();
+ config.manualRule.enabled = true;
+ config.manualRule.pkg = "android";
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ config.manualRule.conditionId = ZenModeConfig.toTimeCondition(mContext, 200, mUserId).id;
+ config.manualRule.enabler = "test";
+
+ // write out entire config xml
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais);
+
+ // The result should have a manual rule; it should have a non-null ZenPolicy and a condition
+ // whose state is true. The conditionId and enabler data should also be preserved.
+ assertThat(fromXml.manualRule).isNotNull();
+ assertThat(fromXml.manualRule.zenPolicy).isNotNull();
+ assertThat(fromXml.manualRule.condition).isNotNull();
+ assertThat(fromXml.manualRule.condition.state).isEqualTo(STATE_TRUE);
+ assertThat(fromXml.manualRule.conditionId).isEqualTo(config.manualRule.conditionId);
+ assertThat(fromXml.manualRule.enabler).isEqualTo("test");
+ assertThat(fromXml.isManualActive()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testConfigXml_manualRule_doesNotTurnOnIfNotUpgrade() throws Exception {
+ // confirm that if the manual rule is already properly set up for modes_ui, it does not get
+ // turned on (set to condition with STATE_TRUE) when reading xml.
+
+ // getMutedAllConfig sets up the manual rule with a policy muting everything
+ ZenModeConfig config = getMutedAllConfig();
+ config.manualRule.condition = new Condition(Uri.EMPTY, "", STATE_FALSE, SOURCE_USER_ACTION);
+ assertThat(config.isManualActive()).isFalse();
+
+ // write out entire config xml
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais);
+
+ // The result should have a manual rule; it should not be changed from the previous rule.
+ assertThat(fromXml.manualRule).isEqualTo(config.manualRule);
+ assertThat(fromXml.isManualActive()).isFalse();
+ }
+
+ @Test
public void testGetDescription_off() {
ZenModeConfig config = new ZenModeConfig();
if (!modesUi()) {
@@ -1238,4 +1292,25 @@
parser.nextTag();
return ZenModeConfig.readZenPolicyXml(parser);
}
+
+ private void writeConfigXml(ZenModeConfig config, Integer version, boolean forBackup,
+ ByteArrayOutputStream os) throws IOException {
+ String tag = ZEN_TAG;
+
+ TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(new BufferedOutputStream(os), "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, tag);
+ config.writeXml(out, version, forBackup);
+ out.endTag(null, tag);
+ out.endDocument();
+ }
+
+ private ZenModeConfig readConfigXml(ByteArrayInputStream is)
+ throws XmlPullParserException, IOException {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(is), null);
+ parser.nextTag();
+ return ZenModeConfig.readXml(parser);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index d7bae45..776a840 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -46,6 +46,7 @@
import static android.app.NotificationManager.Policy.STATE_PRIORITY_CHANNELS_BLOCKED;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
@@ -54,12 +55,12 @@
import static android.service.notification.Condition.SOURCE_USER_ACTION;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
-import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT;
+import static android.service.notification.ZenModeConfig.ORIGIN_INIT_USER;
+import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
+import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
@@ -131,7 +132,6 @@
import android.media.VolumePolicy;
import android.net.Uri;
import android.os.Parcel;
-import android.os.Process;
import android.os.SimpleClock;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
@@ -186,9 +186,6 @@
import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParserException;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -208,6 +205,9 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWith(ParameterizedAndroidJunit4.class)
@@ -338,27 +338,6 @@
mZenModeEventLogger.reset();
}
- private enum ChangeOrigin {
- ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN),
- ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT),
- ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER),
- ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER),
- ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP),
- ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI),
- ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP);
-
- private final int mValue;
-
- ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) {
- mValue = value;
- }
-
- @ZenModeConfig.ConfigChangeOrigin
- public int value() {
- return mValue;
- }
- }
-
private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
String xml = "<zen version=\"10\">\n"
+ "<allow alarms=\"true\" media=\"true\" system=\"false\" calls=\"true\" "
@@ -395,8 +374,8 @@
mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL);
serializer.endDocument();
serializer.flush();
- mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml",
- Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(new ZenModeConfig(), null, ORIGIN_INIT, "writing xml",
+ SYSTEM_UID);
return baos;
}
@@ -411,8 +390,8 @@
serializer.flush();
ZenModeConfig newConfig = new ZenModeConfig();
newConfig.user = userId;
- mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml",
- Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(newConfig, null, ORIGIN_INIT, "writing xml",
+ SYSTEM_UID);
return baos;
}
@@ -705,13 +684,13 @@
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID);
+ azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
// Enable rule
mZenModeHelper.setAutomaticZenRuleState(ruleId,
new Condition(azr.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Confirm that the consolidated policy doesn't allow anything
NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy();
@@ -739,13 +718,13 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID);
+ azr, ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
// Enable rule
mZenModeHelper.setAutomaticZenRuleState(ruleId,
new Condition(azr.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Confirm that the consolidated policy allows only alarms and media and nothing else
NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy();
@@ -819,7 +798,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
Policy totalSilence = new Policy(0, 0, 0);
- mZenModeHelper.setNotificationPolicy(totalSilence, UPDATE_ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(totalSilence, ORIGIN_APP, 1);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
// 2. verify ringer is unchanged
@@ -857,7 +836,7 @@
// in priority only mode:
// ringtone, notification and system streams are affected by ringer mode
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- UPDATE_ORIGIN_APP, "test", "caller", 1);
+ ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -873,9 +852,9 @@
// even when ringer is muted (since all ringer sounds cannot bypass DND),
// system stream is still affected by ringer mode
- mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), UPDATE_ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), ORIGIN_APP, 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- UPDATE_ORIGIN_APP, "test", "caller", 1);
+ ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
mZenModeHelper.new RingerModeDelegate();
@@ -983,7 +962,7 @@
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
for (int i = 0; i < 3; i++) {
mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
- UPDATE_ORIGIN_APP, "test", "caller", 1);
+ ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -1008,7 +987,7 @@
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
- UPDATE_ORIGIN_APP, "test", "caller", 1);
+ ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -1033,7 +1012,7 @@
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY,
- UPDATE_ORIGIN_APP, "test", "caller", 1);
+ ORIGIN_APP, "test", "caller", 1);
}
verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -1048,7 +1027,8 @@
reset(mAudioManager);
// Turn manual zen mode on
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP,
null, "test", CUSTOM_PKG_UID);
// audio manager shouldn't do anything until the handler processes its messages
@@ -1076,14 +1056,14 @@
mZenModeHelper.setManualZenMode(
ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null,
- UPDATE_ORIGIN_APP,
+ ORIGIN_APP,
null,
"test",
CUSTOM_PKG_UID);
mZenModeHelper.setManualZenMode(
ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null,
- UPDATE_ORIGIN_APP,
+ ORIGIN_APP,
null,
"test",
CUSTOM_PKG_UID);
@@ -1108,15 +1088,15 @@
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
| PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
- UPDATE_ORIGIN_UNKNOWN,
+ ORIGIN_UNKNOWN,
1);
mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.setShouldDisplayGrayscale(true)
.setShouldUseNightMode(true)
- .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
+ .build(), ORIGIN_UNKNOWN, "test", 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
+ ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig actual = mZenModeHelper.mConfig.copy();
@@ -1130,13 +1110,13 @@
| PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
CONVERSATION_SENDERS_ANYONE),
- UPDATE_ORIGIN_UNKNOWN, 1);
+ ORIGIN_UNKNOWN, 1);
mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.setShouldDisplayGrayscale(true)
- .build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
+ .build(), ORIGIN_UNKNOWN, "test", 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
- UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
+ ORIGIN_UNKNOWN, "test", "me", 1);
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
if (Flags.modesUi()) {
@@ -1157,7 +1137,7 @@
@Test
public void testProto() throws InvalidProtocolBufferException {
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM,
null, "test", CUSTOM_PKG_UID);
mZenModeHelper.mConfig.automaticRules = new ArrayMap<>(); // no automatic rules
@@ -1381,7 +1361,7 @@
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test",
+ mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, ORIGIN_APP, "test",
CUSTOM_PKG_UID);
assertTrue(-1
== mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
@@ -1410,7 +1390,8 @@
public void testProtoWithManualRule() throws Exception {
setupZenConfig();
mZenModeHelper.mConfig.automaticRules = getCustomAutomaticRules();
- mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(INTERRUPTION_FILTER_PRIORITY, Uri.EMPTY,
+ ORIGIN_APP,
"test", "me", 1);
List<StatsEvent> events = new LinkedList<>();
@@ -1437,14 +1418,14 @@
Policy policy = new Policy(PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_ALARMS, 0, 0);
config10.applyNotificationPolicy(policy);
config10.user = 10;
- mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml",
- Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(config10, null, ORIGIN_INIT, "writeXml",
+ SYSTEM_UID);
ZenModeConfig config11 = mZenModeHelper.mConfig.copy();
config11.user = 11;
policy = new Policy(0, 0, 0);
config11.applyNotificationPolicy(policy);
- mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml",
- Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(config11, null, ORIGIN_INIT, "writeXml",
+ SYSTEM_UID);
// Backup user 10 and reset values.
ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10);
@@ -1514,6 +1495,30 @@
}
@Test
+ public void testReadXmlRestore_doesNotEnableManualRule() throws Exception {
+ setupZenConfig();
+
+ // Turn on manual zen mode
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_USER_IN_SYSTEMUI, "", "someCaller", SYSTEM_UID);
+ ZenModeConfig original = mZenModeHelper.mConfig.copy();
+ assertThat(original.isManualActive()).isTrue();
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(null);
+ TypedXmlPullParser parser = getParserForByteStream(baos);
+ mZenModeHelper.readXml(parser, true, UserHandle.USER_ALL);
+
+ ZenModeConfig result = mZenModeHelper.getConfig();
+ assertThat(result.isManualActive()).isFalse();
+
+ // confirm that we do still keep policy information, modes_ui only; prior to modes_ui the
+ // entire rule is intentionally cleared
+ if (Flags.modesUi()) {
+ assertThat(result.manualRule.zenPolicy).isNotNull();
+ }
+ }
+
+ @Test
public void testWriteXmlWithZenPolicy() throws Exception {
final String ruleId = "customRule";
setupZenConfig();
@@ -2307,7 +2312,7 @@
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
// We need the package name to be something that's not "android" so there aren't any
// existing rules under that package.
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
"test", CUSTOM_PKG_UID);
assertNotNull(id);
}
@@ -2318,7 +2323,7 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
"test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
@@ -2339,7 +2344,7 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
"test", CUSTOM_PKG_UID);
assertNotNull(id);
}
@@ -2350,7 +2355,7 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
"test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
@@ -2371,7 +2376,7 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
"test", CUSTOM_PKG_UID);
assertNotNull(id);
}
@@ -2382,7 +2387,7 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP,
+ String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, ORIGIN_APP,
"test", CUSTOM_PKG_UID);
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
@@ -2399,7 +2404,7 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2420,7 +2425,7 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
assertTrue(id != null);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2446,7 +2451,7 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Zen rule with partially-filled policy: should get all of the filled fields set, and the
// rest filled with default state
@@ -2461,7 +2466,7 @@
.build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// rule 1 should exist
assertThat(id1).isNotNull();
@@ -2506,11 +2511,11 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
CUSTOM_PKG_UID);
mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(),
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_APP,
+ ORIGIN_APP,
CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
@@ -2526,7 +2531,7 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
CUSTOM_PKG_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW",
@@ -2536,7 +2541,7 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(id, zenRule2, ORIGIN_APP, "", CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertEquals("NEW", ruleInConfig.name);
@@ -2551,7 +2556,7 @@
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
CUSTOM_PKG_UID);
assertTrue(id != null);
@@ -2559,7 +2564,7 @@
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "test", CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2571,7 +2576,7 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test",
+ String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, ORIGIN_APP, "test",
CUSTOM_PKG_UID);
assertTrue(id != null);
@@ -2579,7 +2584,7 @@
assertTrue(ruleInConfig != null);
assertEquals(zenRule.getName(), ruleInConfig.name);
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test",
+ mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP, "test",
CUSTOM_PKG_UID);
assertNull(mZenModeHelper.mConfig.automaticRules.get(id));
}
@@ -2596,17 +2601,17 @@
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
Condition condition = new Condition(sharedUri, "", STATE_TRUE);
mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2621,7 +2626,7 @@
condition = new Condition(sharedUri, "", STATE_FALSE);
mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2656,7 +2661,7 @@
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- UPDATE_ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2689,7 +2694,7 @@
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
@@ -2716,7 +2721,7 @@
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- UPDATE_ORIGIN_USER,
+ ORIGIN_USER_IN_SYSTEMUI,
"reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -2736,7 +2741,7 @@
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2748,7 +2753,7 @@
.setOwner(OWNER)
.setDeviceEffects(updateFromApp)
.build(),
- UPDATE_ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
@@ -2770,7 +2775,7 @@
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2780,7 +2785,7 @@
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromSystem)
.build(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
@@ -2797,7 +2802,7 @@
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true)
@@ -2810,7 +2815,7 @@
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromUser)
.build(),
- UPDATE_ORIGIN_USER, "reasons", 0);
+ ORIGIN_USER_IN_SYSTEMUI, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -2829,13 +2834,13 @@
.allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
.build())
.build(),
- UPDATE_ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", 0);
mZenModeHelper.updateAutomaticZenRule(ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
// no zen policy
.build(),
- UPDATE_ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
@@ -2857,7 +2862,7 @@
.allowReminders(true)
.build())
.build(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", 0);
mZenModeHelper.updateAutomaticZenRule(ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -2865,7 +2870,7 @@
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
.build())
.build(),
- UPDATE_ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", 0);
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
@@ -2893,7 +2898,8 @@
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
+ ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(bedtimeRuleId);
@@ -2913,7 +2919,8 @@
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
+ ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
@@ -2934,7 +2941,8 @@
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime,
+ ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.keySet()).containsExactly(
@@ -2948,15 +2956,15 @@
// note that caller=null because that's how it comes in from NMS.setZenMode
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "", null, Process.SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+ "", null, SYSTEM_UID);
assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -2965,28 +2973,29 @@
@EnableFlags(FLAG_MODES_API)
@DisableFlags(FLAG_MODES_UI)
public void setManualZenMode_off_snoozesActiveRules() {
- for (ChangeOrigin origin : ChangeOrigin.values()) {
+ for (ZenChangeOrigin origin : ZenChangeOrigin.values()) {
// Start with an active rule and an inactive rule.
mZenModeHelper.mConfig.automaticRules.clear();
AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
+ ORIGIN_APP,
CUSTOM_PKG_UID);
AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertWithMessage("Failure for origin " + origin.name())
.that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// User turns DND off.
mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
- "snoozing", "systemui", Process.SYSTEM_UID);
+ "snoozing", "systemui", SYSTEM_UID);
assertWithMessage("Failure for origin " + origin.name())
.that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
assertWithMessage("Failure for origin " + origin.name())
@@ -3000,30 +3009,31 @@
@Test
@EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
- public void setManualZenMode_off_doesNotSnoozeRulesIfFromUser() {
- for (ChangeOrigin origin : ChangeOrigin.values()) {
+ public void setManualZenMode_off_doesNotSnoozeRulesIfFromUserInSystemUi() {
+ for (ZenChangeOrigin origin : ZenChangeOrigin.values()) {
// Start with an active rule and an inactive rule
mZenModeHelper.mConfig.automaticRules.clear();
AutomaticZenRule activeRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String activeRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- activeRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ activeRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(activeRuleId, CONDITION_TRUE,
+ ORIGIN_APP,
CUSTOM_PKG_UID);
AutomaticZenRule inactiveRule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String inactiveRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- inactiveRule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ inactiveRule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// User turns DND off.
mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, origin.value(),
- "snoozing", "systemui", Process.SYSTEM_UID);
+ "snoozing", "systemui", SYSTEM_UID);
ZenModeConfig config = mZenModeHelper.mConfig;
- if (origin == ChangeOrigin.ORIGIN_USER) {
+ if (origin == ZenChangeOrigin.ORIGIN_USER_IN_SYSTEMUI) {
// Other rule was unaffected.
assertWithMessage("Failure for origin " + origin.name()).that(
mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
@@ -3048,14 +3058,14 @@
// note that caller=null because that's how it comes in from NMS.setZenMode
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// confirm that setting zen mode via setManualZenMode changed the zen mode correctly
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- null, Process.SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
+ null, SYSTEM_UID);
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@@ -3069,12 +3079,12 @@
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
- Process.SYSTEM_UID);
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ null, SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null,
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "", null,
CUSTOM_PKG_UID);
// In total, this should be 2 loggable changes
@@ -3100,11 +3110,11 @@
assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo(
!(Flags.modesUi() || Flags.modesApi()));
assertTrue(mZenModeEventLogger.getIsUserAction(0));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
// change origin should be populated only under modes_ui
assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
- (Flags.modesApi() && Flags.modesUi()) ? UPDATE_ORIGIN_USER : 0);
+ (Flags.modesApi() && Flags.modesUi()) ? ORIGIN_USER_IN_SYSTEMUI : 0);
// and from turning zen mode off:
// - event ID: DND_TURNED_OFF
@@ -3128,7 +3138,7 @@
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
}
assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+ Flags.modesUi() ? ORIGIN_APP : 0);
}
@Test
@@ -3145,7 +3155,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
// Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
@@ -3153,14 +3163,14 @@
// modes_ui is true, we opt to trust the provided change origin.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
CUSTOM_PKG_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ SYSTEM_UID);
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
null,
@@ -3169,18 +3179,18 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
- Process.SYSTEM_UID);
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "test",
+ SYSTEM_UID);
// Event 3: turn on the system rule
mZenModeHelper.setAutomaticZenRuleState(systemId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Event 4: "User" deletes the rule
mZenModeHelper.removeAutomaticZenRule(systemId,
- Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- Process.SYSTEM_UID);
+ Flags.modesApi() ? ORIGIN_USER_IN_SYSTEMUI : ZenModeConfig.ORIGIN_SYSTEM, "",
+ SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3203,7 +3213,7 @@
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+ Flags.modesUi() ? ORIGIN_APP : 0);
// When the automatic rule is disabled, this should turn off zen mode and also count as a
// user action. We don't care what the consolidated policy is when DND turns off.
@@ -3217,14 +3227,14 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertTrue(mZenModeEventLogger.getIsUserAction(1));
assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(
- Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
+ Flags.modesUi() ? CUSTOM_PKG_UID : SYSTEM_UID);
if (Flags.modesApi()) {
assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
} else {
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
}
assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+ Flags.modesUi() ? ORIGIN_USER_IN_SYSTEMUI : 0);
// When the system rule is enabled, this counts as an automatic action that comes from the
// system and turns on DND
@@ -3233,9 +3243,9 @@
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
assertFalse(mZenModeEventLogger.getIsUserAction(2));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : 0);
+ Flags.modesUi() ? ZenModeConfig.ORIGIN_SYSTEM : 0);
// When the system rule is deleted, we consider this a user action that turns DND off
// (again)
@@ -3244,9 +3254,9 @@
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(3));
assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
assertTrue(mZenModeEventLogger.getIsUserAction(3));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+ Flags.modesUi() ? ORIGIN_USER_IN_SYSTEMUI : 0);
}
@Test
@@ -3264,28 +3274,28 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on manually when the user turns it on in the app
// ("Turn on bedtime now" because user goes to bed earlier).
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_USER_ACTION),
- UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+ ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
// Event 2: App deactivates the rule automatically (it's 8 AM, bedtime schedule ends)
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Event 3: App activates the rule automatically (it's now 11 PM, bedtime schedule starts)
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Event 4: User deactivates the rule manually (they get up before 8 AM on the next day)
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_USER_ACTION),
- UPDATE_ORIGIN_USER, CUSTOM_PKG_UID);
+ ORIGIN_USER_IN_APP, CUSTOM_PKG_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3301,7 +3311,7 @@
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+ Flags.modesUi() ? ORIGIN_USER_IN_APP : 0);
// Automatic rule turned off automatically by app:
// - event ID: DND_TURNED_OFF
@@ -3314,7 +3324,7 @@
assertFalse(mZenModeEventLogger.getIsUserAction(1));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+ Flags.modesUi() ? ORIGIN_APP : 0);
// Automatic rule turned on automatically by app:
// - event ID: DND_TURNED_ON
@@ -3328,7 +3338,7 @@
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
+ Flags.modesUi() ? ORIGIN_APP : 0);
// Automatic rule turned off automatically by the user:
// - event ID: DND_TURNED_ON
@@ -3341,7 +3351,7 @@
assertTrue(mZenModeEventLogger.getIsUserAction(3));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
- Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
+ Flags.modesUi() ? ORIGIN_USER_IN_APP : 0);
}
@Test
@@ -3352,21 +3362,21 @@
// First just turn zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_USER, "", null, Process.SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "", null, SYSTEM_UID);
// Now change the policy slightly; want to confirm that this'll be reflected in the logs
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
- UPDATE_ORIGIN_USER, Process.SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
- null, Process.SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM, "",
+ null, SYSTEM_UID);
// Change the policy again
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0),
- UPDATE_ORIGIN_USER, Process.SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
// Total events: we only expect ones for turning on, changing policy, and turning off
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
@@ -3386,7 +3396,7 @@
mZenModeEventLogger.getEventId(1));
assertEquals(DNDProtoEnums.UNKNOWN_RULE, mZenModeEventLogger.getChangedRuleType(1));
assertTrue(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
assertEquals(STATE_ALLOW, dndProto.getAlarms().getNumber());
assertEquals(STATE_DISALLOW, dndProto.getRepeatCallers().getNumber());
@@ -3410,7 +3420,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Rule 2, same as rule 1
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3420,7 +3430,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Rule 3, has stricter settings than the default settings
ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy();
@@ -3432,27 +3442,27 @@
ruleConfig.getZenPolicy(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// First: turn on rule 1
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Second: turn on rule 2
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Third: turn on rule 3
mZenModeHelper.setAutomaticZenRuleState(id3,
new Condition(zenRule3.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Fourth: Turn *off* rule 2
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// This should result in a total of four events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3465,7 +3475,7 @@
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertFalse(mZenModeEventLogger.getIsUserAction(0));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
// Event 2: rule 2 turns on. This should not change anything about the policy, so the only
@@ -3474,7 +3484,7 @@
mZenModeEventLogger.getEventId(1));
assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
// Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
@@ -3484,7 +3494,7 @@
mZenModeEventLogger.getEventId(2));
assertEquals(3, mZenModeEventLogger.getNumRulesActive(2));
assertFalse(mZenModeEventLogger.getIsUserAction(2));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(2);
assertEquals(STATE_DISALLOW, dndProto.getReminders().getNumber());
assertEquals(STATE_DISALLOW, dndProto.getCalls().getNumber());
@@ -3508,7 +3518,7 @@
// Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
// given that we don't have any zen rules active.
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true);
+ mZenModeHelper.evaluateZenModeLocked(ORIGIN_UNKNOWN, "test", true);
// Check that the change actually took: zen mode should be off now
assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
@@ -3537,7 +3547,7 @@
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID);
+ ORIGIN_APP, "test", SYSTEM_UID);
// Rule 2, same as rule 1 but owned by the system
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
@@ -3547,7 +3557,7 @@
manualRulePolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_USER, "test", Process.SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "test", SYSTEM_UID);
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
@@ -3555,23 +3565,24 @@
// When modes_ui is true: we expect the change origin to be the source of truth.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- Process.SYSTEM_UID);
+ Flags.modesUi() ? ORIGIN_APP : ZenModeConfig.ORIGIN_SYSTEM,
+ SYSTEM_UID);
// Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
// (nor even looked up; the mock PackageManager won't handle "android" as input).
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Disable rule 1. Because this looks like a user action, the UID should not be modified
// from the system-provided one unless modes_ui is true.
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- UPDATE_ORIGIN_USER, "", Process.SYSTEM_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "", SYSTEM_UID);
// Add a manual rule. Any manual rule changes should not get calling uids reassigned.
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP,
"", null, CUSTOM_PKG_UID);
// Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
@@ -3579,7 +3590,7 @@
// Note that this probably shouldn't be able to occur in real scenarios.
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
- UPDATE_ORIGIN_APP, 12345);
+ ORIGIN_APP, 12345);
// That was 5 events total
assertEquals(5, mZenModeEventLogger.numLoggedChanges());
@@ -3598,7 +3609,7 @@
mZenModeEventLogger.getEventId(1));
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+ assertEquals(SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
// Third event: disable rule 1. This looks like a user action so UID should be left alone.
// When modes_ui is true, we assign log this user action with the app that owns the rule.
@@ -3607,7 +3618,7 @@
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
assertTrue(mZenModeEventLogger.getIsUserAction(2));
assertThat(mZenModeEventLogger.getPackageUid(2)).isEqualTo(
- Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
+ Flags.modesUi() ? CUSTOM_PKG_UID : SYSTEM_UID);
// Fourth event: turns on manual mode. Doesn't change effective policy so this is just a
// change in active rules. Confirm that the package UID is left unchanged.
@@ -3636,29 +3647,30 @@
// Turn on zen mode with a manual rule with an enabler set. This should *not* count
// as a user action, and *should* get its UID reassigned.
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "", CUSTOM_PKG_NAME, SYSTEM_UID);
assertEquals(1, mZenModeEventLogger.numLoggedChanges());
// Now change apps bypassing to true
ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
newConfig.areChannelsBypassingDnd = true;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
// and then back to false, all without changing anything else
newConfig.areChannelsBypassingDnd = false;
mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(3, mZenModeEventLogger.numLoggedChanges());
// Turn off manual mode, call from a package: don't reset UID even though enabler is set
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "",
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "",
CUSTOM_PKG_NAME, 12345);
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
// And likewise when turning it back on again
- mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
+ ORIGIN_APP,
"", CUSTOM_PKG_NAME, 12345);
// These are 5 events in total.
@@ -3705,7 +3717,7 @@
// First just turn zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Now change only the channels part of the policy; want to confirm that this'll be
// reflected in the logs
@@ -3716,7 +3728,7 @@
STATE_PRIORITY_CHANNELS_BLOCKED,
oldPolicy.priorityConversationSenders);
mZenModeHelper.setNotificationPolicy(newPolicy,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Total events: one for turning on, one for changing policy
assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
@@ -3757,17 +3769,17 @@
null,
NotificationManager.INTERRUPTION_FILTER_ALL, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: App activates the rule automatically.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Event 2: App deactivates the rule automatically.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// In total, this represents 2 events.
assertEquals(2, mZenModeEventLogger.numLoggedChanges());
@@ -3798,7 +3810,8 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime, UPDATE_ORIGIN_APP,
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime,
+ ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
// Create immersive rule
@@ -3806,27 +3819,28 @@
.setType(TYPE_IMMERSIVE)
.setZenPolicy(mZenModeHelper.mConfig.getZenPolicy()) // same as the manual rule
.build();
- String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive, UPDATE_ORIGIN_APP,
+ String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive,
+ ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
// Event 1: Activate bedtime rule. This doesn't turn on notification filtering
mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Event 2: turn on manual zen mode. Manual rule will have ACTIVE_RULE_TYPE_MANUAL
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "", null, SYSTEM_UID);
// Event 3: Turn immersive on
mZenModeHelper.setAutomaticZenRuleState(immersiveId,
new Condition(immersive.getConditionId(), "", STATE_TRUE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Event 4: Turn off bedtime mode, leaving just manual + immersive
mZenModeHelper.setAutomaticZenRuleState(bedtimeRuleId,
new Condition(bedtime.getConditionId(), "", STATE_FALSE, SOURCE_SCHEDULE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Total of 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3887,12 +3901,12 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
assertEquals(mZenModeHelper.getNotificationPolicy(),
mZenModeHelper.getConsolidatedNotificationPolicy());
@@ -3923,12 +3937,12 @@
null, // null policy
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// inspect the consolidated policy, which should match the device default settings.
assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy))
@@ -3961,12 +3975,12 @@
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values (from device default or
@@ -4006,12 +4020,12 @@
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// since this is the only active rule, the consolidated policy should match the custom
// policy for every field specified, and take default values (from either device default
@@ -4046,12 +4060,12 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 1
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4070,12 +4084,12 @@
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -4107,12 +4121,12 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 1
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// custom policy for rule 2
ZenPolicy customPolicy = new ZenPolicy.Builder()
@@ -4131,12 +4145,12 @@
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// now both rules should be on, and the consolidated policy should reflect the most
// restrictive option of each of the two
@@ -4173,12 +4187,12 @@
customPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable the rule; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// confirm that channels make it through
assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4195,12 +4209,12 @@
strictPolicy,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// enable rule 2; this will update the consolidated policy
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// rule 2 should override rule 1
assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels());
@@ -4226,10 +4240,10 @@
.build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ zenRuleWithPriority, ORIGIN_APP, "test", CUSTOM_PKG_UID);
mZenModeHelper.setAutomaticZenRuleState(rule1Id,
new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Rule 2: ALL, but somehow with a super strict ZenPolicy.
AutomaticZenRule zenRuleWithAll = new AutomaticZenRule("All",
@@ -4239,10 +4253,10 @@
new ZenPolicy.Builder().disallowAllSounds().build(),
NotificationManager.INTERRUPTION_FILTER_ALL, true);
String rule2Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRuleWithAll, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
+ zenRuleWithAll, ORIGIN_APP, "test", CUSTOM_PKG_UID);
mZenModeHelper.setAutomaticZenRuleState(rule2Id,
new Condition(zenRuleWithPriority.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
// Consolidated Policy should be default + rule1.
assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo(
@@ -4321,7 +4335,7 @@
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
- UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add", CUSTOM_PKG_UID);
ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
@@ -4351,15 +4365,15 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// Checks the name can be changed by the app because the user has not modified it.
AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("NewName");
@@ -4368,16 +4382,16 @@
azrUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
// ...but the app can still modify the name, because the name itself hasn't been modified
// by the user.
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewAppName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("NewAppName");
@@ -4385,8 +4399,8 @@
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("UserProvidedName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
@@ -4394,8 +4408,8 @@
azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewAppName")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
}
@@ -4411,7 +4425,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// Modifies the filter, icon, zen policy, and device effects
@@ -4430,8 +4444,8 @@
.build();
// Update the rule with the AZR from origin user.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
@@ -4468,7 +4482,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// Modifies the icon, zen policy and device effects
@@ -4487,8 +4501,8 @@
.build();
// Update the rule with the AZR from origin systemUI.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "reason", Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ZenModeConfig.ORIGIN_SYSTEM,
+ "reason", SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
@@ -4518,7 +4532,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
ZenPolicy policy = new ZenPolicy.Builder()
@@ -4535,8 +4549,8 @@
// Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
// The bitmask is not modified.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, ORIGIN_APP, "reason",
+ SYSTEM_UID);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.userModifiedFields).isEqualTo(0);
@@ -4551,7 +4565,7 @@
// Creates another rule, this time from user. This will have user modified bits set.
String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
int ruleModifiedFields = storedRule.userModifiedFields;
int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
@@ -4559,8 +4573,8 @@
// Zen rule update coming from the app again. This cannot fully update the rule, because
// the rule is already considered user modified.
- mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP,
- "reason", Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, ORIGIN_APP,
+ "reason", SYSTEM_UID);
AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
// The app can only change the value if the rule is not already user modified,
@@ -4591,7 +4605,7 @@
.build())
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// The values are modified but the bitmask is not.
@@ -4613,7 +4627,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Sets Device Effects to null
@@ -4622,8 +4636,8 @@
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+ SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept.
@@ -4639,7 +4653,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
@@ -4649,8 +4663,8 @@
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_APP, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
@@ -4671,7 +4685,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// Create a fully populated ZenPolicy.
@@ -4700,8 +4714,8 @@
// Applies the update to the rule.
// Default config defined in getDefaultConfigParser() is used as the original rule.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// New ZenPolicy differs from the default config
@@ -4732,7 +4746,7 @@
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
+ azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
@@ -4743,8 +4757,8 @@
.build();
// Applies the update to the rule.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// New ZenDeviceEffects is used; all fields considered set, since previously were null.
@@ -4769,7 +4783,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4785,8 +4799,8 @@
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "", Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ "", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
@@ -4804,7 +4818,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4820,8 +4834,8 @@
mZenModeHelper.addCallback(callback);
zenRule.setEnabled(true);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "", Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ "", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
@@ -4840,7 +4854,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[1];
@@ -4857,7 +4871,7 @@
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4880,7 +4894,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -4899,10 +4913,10 @@
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- null, "", Process.SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+ null, "", SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4925,7 +4939,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
CountDownLatch latch = new CountDownLatch(1);
final int[] actualStatus = new int[2];
@@ -4944,11 +4958,11 @@
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_FALSE),
- UPDATE_ORIGIN_APP, Process.SYSTEM_UID);
+ ORIGIN_APP, SYSTEM_UID);
assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
if (CompatChanges.isChangeEnabled(ZenModeHelper.SEND_ACTIVATION_AZR_STATUSES)) {
@@ -4970,21 +4984,21 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ zenRule, ZenModeConfig.ORIGIN_SYSTEM, "test", SYSTEM_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
mZenModeHelper.setAutomaticZenRuleState(createdId,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, SYSTEM_UID);
// Event 2: Snooze rule by turning off DND
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "", null, Process.SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+ "", null, SYSTEM_UID);
// Event 3: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
- mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "", Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM,
+ "", SYSTEM_UID);
assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
}
@@ -4997,16 +5011,16 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
.setTriggerDescription("Whenever")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_APP, "reason",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
@@ -5021,14 +5035,14 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, ORIGIN_APP, "reason",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
@@ -5044,17 +5058,17 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
.setTriggerDescription("Whenever")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
+ "reason", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5074,15 +5088,15 @@
.build();
String ruleId =
mZenModeHelper.addAutomaticZenRule(
- mPkg, rule, UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ mPkg, rule, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
mZenModeHelper.setAutomaticZenRuleState(
- ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ruleId, CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
AutomaticZenRule updateWithDiff =
new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason",
- CUSTOM_PKG_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, ORIGIN_USER_IN_SYSTEMUI,
+ "reason", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
@@ -5097,18 +5111,18 @@
.setConfigurationActivity(new ComponentName(mPkg, "cls"))
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "reason",
CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
mZenModeHelper.updateAutomaticZenRule(ruleId,
- new AutomaticZenRule.Builder(rule).setEnabled(false).build(), UPDATE_ORIGIN_USER,
- "disable", CUSTOM_PKG_UID);
+ new AutomaticZenRule.Builder(rule).setEnabled(false).build(),
+ ORIGIN_USER_IN_SYSTEMUI, "disable", SYSTEM_UID);
mZenModeHelper.updateAutomaticZenRule(ruleId,
- new AutomaticZenRule.Builder(rule).setEnabled(true).build(), UPDATE_ORIGIN_USER,
- "enable", CUSTOM_PKG_UID);
+ new AutomaticZenRule.Builder(rule).setEnabled(true).build(),
+ ORIGIN_USER_IN_SYSTEMUI, "enable", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
@@ -5123,13 +5137,13 @@
.setOwner(new ComponentName("android", "some.old.cps"))
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule("android", original,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "reason", SYSTEM_UID);
AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName("android", "brand.new.cps"))
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, update, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(result).isNotNull();
@@ -5143,13 +5157,13 @@
.setOwner(new ComponentName(mContext.getPackageName(), "old.third.party.cps"))
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), original,
- UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule update = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setOwner(new ComponentName(mContext.getPackageName(), "new.third.party.cps"))
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, update, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason",
+ SYSTEM_UID);
AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(result).isNotNull();
@@ -5166,24 +5180,24 @@
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
.build());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(any(), eq(UPDATE_ORIGIN_APP));
+ verify(mDeviceEffectsApplier).apply(any(), eq(ORIGIN_APP));
// Now delete the (currently active!) rule. For example, assume this is done from settings.
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "remove",
- Process.SYSTEM_UID);
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "remove",
+ SYSTEM_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_USER));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_USER_IN_SYSTEMUI));
}
@Test
@EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_applied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
@@ -5192,11 +5206,11 @@
String ruleId = addRuleWithEffects(effects);
verifyNoMoreInteractions(mDeviceEffectsApplier);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP));
+ verify(mDeviceEffectsApplier).apply(eq(effects), eq(ORIGIN_APP));
}
@Test
@@ -5206,30 +5220,30 @@
ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
+ verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_APP));
}
@Test
@EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_changeToConsolidatedEffects_applied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
String ruleId = addRuleWithEffects(
new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.addExtraEffect("ONE")
.build());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(
@@ -5237,7 +5251,7 @@
.setShouldDisplayGrayscale(true)
.addExtraEffect("ONE")
.build()),
- eq(UPDATE_ORIGIN_APP));
+ eq(ORIGIN_APP));
// Now create and activate a second rule that adds more effects.
String secondRuleId = addRuleWithEffects(
@@ -5245,7 +5259,7 @@
.setShouldDimWallpaper(true)
.addExtraEffect("TWO")
.build());
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
@@ -5255,28 +5269,28 @@
.setShouldDimWallpaper(true)
.setExtraEffects(ImmutableSet.of("ONE", "TWO"))
.build()),
- eq(UPDATE_ORIGIN_APP));
+ eq(ORIGIN_APP));
}
@Test
@EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
ZenDeviceEffects zde = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true)
.addExtraEffect("extra_effect")
.build();
String ruleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
- verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP));
+ verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_APP));
// Now create and activate a second rule that doesn't add any more effects.
String secondRuleId = addRuleWithEffects(zde);
- mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
@@ -5290,20 +5304,20 @@
String ruleId = addRuleWithEffects(zde);
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier, never()).apply(any(), anyInt());
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
- verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT));
+ verify(mDeviceEffectsApplier).apply(eq(zde), eq(ORIGIN_INIT));
}
@Test
@EnableFlags(FLAG_MODES_API)
public void testDeviceEffects_onUserSwitch_appliedImmediately() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
- verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT));
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(ORIGIN_INIT));
// Initialize default configurations (default rules) for both users.
mZenModeHelper.onUserSwitched(1);
@@ -5324,7 +5338,7 @@
mTestableLooper.processAllMessages();
verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects),
- eq(UPDATE_ORIGIN_INIT_USER));
+ eq(ORIGIN_INIT_USER));
}
private String addRuleWithEffects(ZenDeviceEffects effects) {
@@ -5332,7 +5346,7 @@
.setDeviceEffects(effects)
.build();
return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
}
@Test
@@ -5346,7 +5360,7 @@
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
// User customizes it.
@@ -5354,12 +5368,12 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5367,7 +5381,7 @@
// App adds it again.
mTestClock.advanceByMillis(1000);
String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was restored:
// - id and creation time is the same as the original one.
@@ -5401,12 +5415,12 @@
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
@@ -5414,7 +5428,7 @@
// App adds it again.
mTestClock.advanceByMillis(1000);
String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was recreated. This means id and creation time are new.
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
@@ -5433,7 +5447,7 @@
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
// User customizes it.
@@ -5442,12 +5456,12 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
// App deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5455,7 +5469,7 @@
// User creates it again (unusual case, but ok).
mTestClock.advanceByMillis(1000);
String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_USER, "add it anew", CUSTOM_PKG_UID);
+ ORIGIN_USER_IN_SYSTEMUI, "add it anew", SYSTEM_UID);
// Verify that the rule was recreated. This means id and creation time are new, and the rule
// matches the latest data supplied to addAZR.
@@ -5481,7 +5495,7 @@
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(false).build())
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
// User customizes it.
@@ -5490,20 +5504,20 @@
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
// User deletes it.
mTestClock.advanceByMillis(1000);
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "delete it",
- CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_USER_IN_SYSTEMUI, "delete it",
+ SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
// App creates it again.
mTestClock.advanceByMillis(1000);
String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// Verify that the rule was recreated. This means id and creation time are new.
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
@@ -5520,29 +5534,34 @@
// Start with a bunch of customized rules where conditionUris are not unique.
String id1 = mZenModeHelper.addAutomaticZenRule("pkg1",
- new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
String id2 = mZenModeHelper.addAutomaticZenRule("pkg1",
- new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
String id3 = mZenModeHelper.addAutomaticZenRule("pkg1",
- new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test3", Uri.parse("uri2")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
String id4 = mZenModeHelper.addAutomaticZenRule("pkg2",
- new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test4", Uri.parse("uri1")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
String id5 = mZenModeHelper.addAutomaticZenRule("pkg2",
- new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test5", Uri.parse("uri1")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- mZenModeHelper.removeAutomaticZenRule(id1, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id2, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id3, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id4, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
- mZenModeHelper.removeAutomaticZenRule(id5, UPDATE_ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id1, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id2, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id3, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id4, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id5, ORIGIN_APP, "begone", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.deletedRules.keySet())
.containsExactly("pkg1|uri1", "pkg1|uri2", "pkg2|uri1");
@@ -5557,17 +5576,19 @@
mZenModeHelper.mConfig.automaticRules.clear();
mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test1", Uri.parse("uri1")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(), UPDATE_ORIGIN_APP,
+ new AutomaticZenRule.Builder("Test2", Uri.parse("uri2")).build(),
+ ORIGIN_APP,
"add it", CUSTOM_PKG_UID);
for (ZenRule zenRule : mZenModeHelper.mConfig.automaticRules.values()) {
zenRule.userModifiedFields = AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
}
- mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP,
+ mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), ORIGIN_APP,
"begone", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(2);
@@ -5586,7 +5607,7 @@
mZenModeHelper.mConfig.deletedRules.put(ZenModeConfig.deletedRuleKey(pkg2Rule), pkg2Rule);
mZenModeHelper.removeAutomaticZenRules("pkg1",
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "goodbye pkg1", Process.SYSTEM_UID);
+ ZenModeConfig.ORIGIN_SYSTEM, "goodbye pkg1", SYSTEM_UID);
// Preserved rules from pkg1 are gone; those from pkg2 are still there.
assertThat(mZenModeHelper.mConfig.deletedRules.values().stream().map(r -> r.pkg)
@@ -5603,23 +5624,23 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App activates it.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// App deletes it.
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
@@ -5627,7 +5648,7 @@
// App adds it again.
String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
@@ -5652,35 +5673,35 @@
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it", CUSTOM_PKG_UID);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, UPDATE_ORIGIN_USER, "userUpdate",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+ "userUpdate", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App activates it.
- mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// User snoozes it.
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
- "snoozing", "systemui", Process.SYSTEM_UID);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ZenModeConfig.ORIGIN_SYSTEM,
+ "snoozing", "systemui", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
// App deletes it.
- mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_APP, "delete it",
+ mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
// App adds it again.
String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
- UPDATE_ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+ ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
@@ -5762,20 +5783,20 @@
.setConfigurationActivity(
new ComponentName(mContext.getPackageName(), "Blah"))
.build(),
- UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
+ ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
// Null condition -> STATE_FALSE
assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+ mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, ORIGIN_APP,
CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
- mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
+ mZenModeHelper.removeAutomaticZenRule(id, ORIGIN_APP, "", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_UNKNOWN);
}
@@ -5788,7 +5809,7 @@
systemRule.condition = new Condition(systemRule.conditionId, "on", Condition.STATE_TRUE);
ZenModeConfig config = mZenModeHelper.mConfig.copy();
config.automaticRules.put("systemRule", systemRule);
- mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.getAutomaticZenRuleState("systemRule")).isEqualTo(
@@ -5805,13 +5826,13 @@
otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
ZenModeConfig config = mZenModeHelper.mConfig.copy();
config.automaticRules.put("otherRule", otherRule);
- mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
mZenModeHelper.setAutomaticZenRuleState("otherRule",
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
}
@@ -5826,13 +5847,13 @@
otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
ZenModeConfig config = mZenModeHelper.mConfig.copy();
config.automaticRules.put("otherRule", otherRule);
- mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
mZenModeHelper.setAutomaticZenRuleState(otherRule.conditionId,
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
- UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ ORIGIN_APP, CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
}
@@ -5851,7 +5872,7 @@
});
Policy totalSilencePolicy = new Policy(0, 0, 0);
- mZenModeHelper.setNotificationPolicy(totalSilencePolicy, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ mZenModeHelper.setNotificationPolicy(totalSilencePolicy, ORIGIN_APP, CUSTOM_PKG_UID);
Policy callbackPolicy = futurePolicy.get(1, TimeUnit.SECONDS);
assertThat(callbackPolicy.allowReminders()).isFalse();
@@ -5874,9 +5895,9 @@
.setOwner(OWNER)
.setInterruptionFilter(INTERRUPTION_FILTER_NONE)
.build(),
- UPDATE_ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", 0);
mZenModeHelper.setAutomaticZenRuleState(totalSilenceRuleId,
- new Condition(CONDITION_ID, "", STATE_TRUE), UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+ new Condition(CONDITION_ID, "", STATE_TRUE), ORIGIN_APP, CUSTOM_PKG_UID);
Policy callbackPolicy = futureConsolidatedPolicy.get(1, TimeUnit.SECONDS);
assertThat(callbackPolicy.allowMedia()).isFalse();
@@ -5905,7 +5926,7 @@
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -5937,8 +5958,8 @@
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+ "reason", SYSTEM_UID);
// From app, call "setInterruptionFilter" again.
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
@@ -5969,8 +5990,8 @@
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Renamed")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+ "reason", SYSTEM_UID);
// From app, call "setInterruptionFilter" again.
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
@@ -6020,7 +6041,7 @@
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
@@ -6121,8 +6142,8 @@
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setZenPolicy(userUpdateZenPolicy)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+ "reason", SYSTEM_UID);
// From app, call "setNotificationPolicy" again.
Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
@@ -6159,8 +6180,8 @@
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Rule renamed, not touching policy")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, ORIGIN_USER_IN_SYSTEMUI,
+ "reason", SYSTEM_UID);
// From app, call "setNotificationPolicy" again.
Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
@@ -6215,7 +6236,7 @@
// If the policy then changes afterwards, it should inherit updates because user cannot
// edit the policy in the UI.
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_ALARMS, 0, 0),
- UPDATE_ORIGIN_APP, 1);
+ ORIGIN_APP, 1);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
@@ -6228,7 +6249,7 @@
@EnableFlags(FLAG_MODES_API)
public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_STARRED, 0);
- mZenModeHelper.setNotificationPolicy(policy, UPDATE_ORIGIN_USER, 1);
+ mZenModeHelper.setNotificationPolicy(policy, ORIGIN_APP, CUSTOM_PKG_UID);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
@@ -6264,7 +6285,7 @@
ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
- mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+ mZenModeHelper.setNotificationPolicy(newManualPolicy, ORIGIN_APP, CUSTOM_PKG_UID);
ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
// Only app rules with default or same-as-manual policies were updated.
@@ -6294,7 +6315,7 @@
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(),
- UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(storedRule.getIconResId()).isEqualTo(0);
@@ -6306,7 +6327,8 @@
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
+ mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
+ SYSTEM_UID);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6316,13 +6338,14 @@
@Test
@EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
public void setManualZenRuleDeviceEffects_preexistingMode() {
- mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, UPDATE_ORIGIN_USER,
- "create manual rule", "settings", 1000);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, Uri.EMPTY, ORIGIN_USER_IN_SYSTEMUI,
+ "create manual rule", "settings", SYSTEM_UID);
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build();
- mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
+ mZenModeHelper.setManualZenRuleDeviceEffects(effects, ORIGIN_USER_IN_SYSTEMUI, "settings",
+ SYSTEM_UID);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
assertThat(mZenModeHelper.getConfig().isManualActive()).isFalse();
@@ -6337,11 +6360,12 @@
.setEnabled(false)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled, UPDATE_ORIGIN_APP,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsDisabled,
+ ORIGIN_APP,
"new", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_APP);
+ ORIGIN_APP);
}
@Test
@@ -6351,19 +6375,20 @@
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ ORIGIN_APP,
"new", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_UNKNOWN);
+ ORIGIN_UNKNOWN);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
+ SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_USER);
+ ORIGIN_USER_IN_SYSTEMUI);
}
@Test
@@ -6373,26 +6398,27 @@
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ ORIGIN_APP,
"new", CUSTOM_PKG_UID);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
+ SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_USER);
+ ORIGIN_USER_IN_SYSTEMUI);
// Now update it again, for an unrelated reason with a different origin.
AutomaticZenRule nowRenamed = new AutomaticZenRule.Builder(nowDisabled)
.setName("Fancy pants rule")
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, UPDATE_ORIGIN_APP, "update",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, nowRenamed, ORIGIN_APP, "update",
CUSTOM_PKG_UID);
// Identity of the disabler is preserved.
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_USER);
+ ORIGIN_USER_IN_SYSTEMUI);
}
@Test
@@ -6402,26 +6428,27 @@
.setOwner(new ComponentName(mPkg, "SomeProvider"))
.setEnabled(true)
.build();
- String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled, UPDATE_ORIGIN_APP,
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, startsEnabled,
+ ORIGIN_APP,
"new", CUSTOM_PKG_UID);
AutomaticZenRule nowDisabled = new AutomaticZenRule.Builder(startsEnabled)
.setEnabled(false)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, UPDATE_ORIGIN_USER, "off",
- Process.SYSTEM_UID);
+ mZenModeHelper.updateAutomaticZenRule(ruleId, nowDisabled, ORIGIN_USER_IN_SYSTEMUI, "off",
+ SYSTEM_UID);
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_USER);
+ ORIGIN_USER_IN_SYSTEMUI);
// Now enable it again
AutomaticZenRule nowEnabled = new AutomaticZenRule.Builder(nowDisabled)
.setEnabled(true)
.build();
- mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, UPDATE_ORIGIN_APP, "on",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, nowEnabled, ORIGIN_APP, "on",
CUSTOM_PKG_UID);
// Identity of the disabler was cleared.
assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).disabledOrigin).isEqualTo(
- UPDATE_ORIGIN_UNKNOWN);
+ ORIGIN_UNKNOWN);
}
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@@ -6494,7 +6521,7 @@
SUPPRESSED_EFFECT_BADGE,
0,
CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.setNotificationPolicy(customPolicy, UPDATE_ORIGIN_UNKNOWN, 1);
+ mZenModeHelper.setNotificationPolicy(customPolicy, ORIGIN_UNKNOWN, 1);
if (!Flags.modesUi()) {
mZenModeHelper.mConfig.manualRule = null;
}
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 757bcd8..43ad44f 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -32,6 +32,7 @@
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
+ "junit-params",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
"service-permission.stubs.system_server",
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 59d5577..1493253 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.hardware.vibrator.IVibrator;
import android.os.CombinedVibration;
@@ -32,11 +33,12 @@
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.SparseArray;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import com.android.server.LocalServices;
@@ -76,9 +78,10 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+ Context context = ApplicationProvider.getApplicationContext();
mTestLooper = new TestLooper();
- mVibrationSettings = new VibrationSettings(
- InstrumentationRegistry.getContext(), new Handler(mTestLooper.getLooper()));
+ mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
+ new VibrationConfig(context.getResources()));
SparseArray<VibratorController> vibrators = new SparseArray<>();
vibrators.put(EMPTY_VIBRATOR_ID, createEmptyVibratorController(EMPTY_VIBRATOR_ID));
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index 2b23b18..e0d05df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -16,16 +16,17 @@
package com.android.server.vibrator;
-
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_CLICK;
+import static com.android.internal.R.xml.haptic_feedback_customization;
import static com.android.server.vibrator.HapticFeedbackCustomization.CustomizationParserException;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
@@ -39,10 +40,15 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -50,6 +56,7 @@
import java.io.File;
import java.io.FileOutputStream;
+@RunWith(JUnitParamsRunner.class)
public class HapticFeedbackCustomizationTest {
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -78,21 +85,35 @@
@Mock private Resources mResourcesMock;
@Mock private VibratorInfo mVibratorInfoMock;
+ @Keep
+ private static Object[][] hapticFeedbackCustomizationTestArguments() {
+ // (boolean hasConfigFile, boolean hasRes).
+ return new Object[][] {{true, true}, {true, false}, {false, true}};
+ }
+
@Before
public void setUp() {
when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
}
@Test
- public void testParseCustomizations_noCustomization_success() throws Exception {
- assertParseCustomizationsSucceeds(
- /* xml= */ "<haptic-feedback-constants></haptic-feedback-constants>",
- /* expectedCustomizations= */ new SparseArray<>());
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_noCustomization_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xml = "<haptic-feedback-constants></haptic-feedback-constants>";
+ SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
+
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_featureFlagDisabled_returnsNull(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED);
// Valid customization XML.
String xml = "<haptic-feedback-constants>"
@@ -100,14 +121,16 @@
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
+ "</haptic-feedback-constants>";
- setupCustomizationFile(xml);
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
.isNull();
}
@Test
- public void testParseCustomizations_oneVibrationCustomization_success() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_oneVibrationCustomization_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
@@ -116,11 +139,13 @@
SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
expectedMapping.put(10, COMPOSITION_VIBRATION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_oneVibrationSelectCustomization_success() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_oneVibrationSelectCustomization_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
@@ -131,11 +156,13 @@
SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
expectedMapping.put(10, COMPOSITION_VIBRATION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_multipleCustomizations_success() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_multipleCustomizations_success(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ COMPOSITION_VIBRATION_XML
@@ -162,11 +189,13 @@
expectedMapping.put(150, PREDEFINED_VIBRATION);
expectedMapping.put(10, WAVEFORM_VIBARTION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success()
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_multipleCustomizations_noSupportedVibration_success(
+ boolean hasConfigFile, boolean hasRes)
throws Exception {
makeUnsupported(COMPOSITION_VIBRATION, PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
String xml = "<haptic-feedback-constants>"
@@ -189,13 +218,16 @@
+ "</vibration-select>"
+ "</constant>"
+ "</haptic-feedback-constants>";
+ SparseArray<VibrationEffect> expectedMapping = new SparseArray<>();
- assertParseCustomizationsSucceeds(xml, new SparseArray<>());
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success()
- throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_multipleCustomizations_someUnsupportedVibration_success(
+ boolean hasConfigFile, boolean hasRes)
+ throws Exception {
makeSupported(PREDEFINED_VIBRATION, WAVEFORM_VIBARTION);
makeUnsupported(COMPOSITION_VIBRATION);
String xml = "<haptic-feedback-constants>"
@@ -230,7 +262,7 @@
expectedMapping.put(150, PREDEFINED_VIBRATION);
expectedMapping.put(10, PREDEFINED_VIBRATION);
- assertParseCustomizationsSucceeds(xml, expectedMapping);
+ assertParseCustomizationsSucceeds(xml, expectedMapping, hasConfigFile, hasRes);
}
@Test
@@ -252,12 +284,23 @@
}
@Test
- public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException()
- throws Exception {
+ public void testParseCustomizations_noCustomizationResource_returnsNull() throws Exception {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ doThrow(new Resources.NotFoundException())
+ .when(mResourcesMock).getXml(haptic_feedback_customization);
+
+ assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
+ .isNull();
+ }
+
+ @Test
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_disallowedVibrationForHapticFeedback_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
// The XML content is good, but the serialized vibration is not supported for haptic
// feedback usage (i.e. repeating vibration).
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xml = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect>"
+ "<waveform-effect>"
@@ -267,127 +310,139 @@
+ "</waveform-effect>"
+ "</vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_emptyXml_throwsException() throws Exception {
- assertParseCustomizationsFails("");
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_emptyXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ assertParseCustomizationsFails("", hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_noVibrationXml_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_noVibrationXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xml = "<haptic-feedback-constants>"
+ "<constant id=\"1\">"
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xml, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_badEffectId_throwsException() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_badEffectId_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
// Negative id
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNegativeId = "<haptic-feedback-constants>"
+ "<constant id=\"-10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// Non-numeral id
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNonNumericalId = "<haptic-feedback-constants>"
+ "<constant id=\"xyz\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlNegativeId, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNonNumericalId, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_malformedXml_throwsException() throws Exception {
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_malformedXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
// No start "<constant>" tag
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNoStartConstantTag = "<haptic-feedback-constants>"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// No end "<constant>" tag
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNoEndConstantTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// No start "<haptic-feedback-constants>" tag
- assertParseCustomizationsFails(
- "<constant id=\"10\">"
+ String xmlNoStartCustomizationTag = "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
+ + "</haptic-feedback-constants>";
// No end "<haptic-feedback-constants>" tag
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ String xmlNoEndCustomizationTag = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
- + "</constant>");
+ + "</constant>";
+
+ assertParseCustomizationsFails(xmlNoStartConstantTag, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNoEndConstantTag, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNoStartCustomizationTag, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlNoEndCustomizationTag, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_badVibrationXml_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_badVibrationXml_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xmlBad1 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<bad-vibration-effect></bad-vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBad2 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBad3 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-select>"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBad4 = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ "<vibration-effect><predefined-effect name=\"bad-effect\"/></vibration-effect>"
+ "</vibration-select>"
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlBad1, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBad2, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBad3, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBad4, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_badConstantAttribute_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_badConstantAttribute_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xmlBadConstantAttribute1 = "<haptic-feedback-constants>"
+ "<constant iddddd=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
-
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ + "</haptic-feedback-constants>";
+ String xmlBadConstantAttribute2 = "<haptic-feedback-constants>"
+ "<constant id=\"10\" unwanted-attr=\"1\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlBadConstantAttribute1, hasConfigFile, hasRes);
+ assertParseCustomizationsFails(xmlBadConstantAttribute2, hasConfigFile, hasRes);
}
@Test
- public void testParseCustomizations_duplicateEffects_throwsException() throws Exception {
- assertParseCustomizationsFails(
- "<haptic-feedback-constants>"
+ @Parameters(method = "hapticFeedbackCustomizationTestArguments")
+ public void testParseCustomizations_duplicateEffects_throwsException(
+ boolean hasConfigFile, boolean hasRes) throws Exception {
+ String xmlDuplicateEffect = "<haptic-feedback-constants>"
+ "<constant id=\"10\">"
+ COMPOSITION_VIBRATION_XML
+ "</constant>"
@@ -397,32 +452,46 @@
+ "<constant id=\"11\">"
+ PREDEFINED_VIBRATION_XML
+ "</constant>"
- + "</haptic-feedback-constants>");
+ + "</haptic-feedback-constants>";
+
+ assertParseCustomizationsFails(xmlDuplicateEffect, hasConfigFile, hasRes);
}
- private void assertParseCustomizationsSucceeds(
- String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
- setupCustomizationFile(xml);
+ private void assertParseCustomizationsSucceeds(String xml,
+ SparseArray<VibrationEffect> expectedCustomizations, boolean hasConfigFile,
+ boolean hasRes) throws Exception {
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
assertThat(expectedCustomizations.contentEquals(
HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
- .isTrue();
+ .isTrue();
}
- private void assertParseCustomizationsFails(String xml) throws Exception {
- setupCustomizationFile(xml);
- assertThrows("Expected haptic feedback customization to fail for " + xml,
- CustomizationParserException.class,
- () -> HapticFeedbackCustomization.loadVibrations(
- mResourcesMock, mVibratorInfoMock));
- }
-
- private void assertParseCustomizationsFails() throws Exception {
+ private void assertParseCustomizationsFails(String xml, boolean hasConfigFile, boolean hasRes)
+ throws Exception {
+ setupParseCustomizations(xml, hasConfigFile, hasRes);
assertThrows("Expected haptic feedback customization to fail",
CustomizationParserException.class,
() -> HapticFeedbackCustomization.loadVibrations(
mResourcesMock, mVibratorInfoMock));
}
+ private void setupParseCustomizations(String xml, boolean hasConfigFile, boolean hasRes)
+ throws Exception {
+ clearFileAndResourceSetup();
+ if (hasConfigFile) {
+ setupCustomizationFile(xml);
+ }
+ if (hasRes) {
+ setupCustomizationResource(xml);
+ }
+ }
+
+ private void clearFileAndResourceSetup() {
+ when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
+ .thenReturn(null);
+ when(mResourcesMock.getXml(haptic_feedback_customization)).thenReturn(null);
+ }
+
private void setupCustomizationFile(String xml) throws Exception {
File file = createFile(xml);
setCustomizationFilePath(file.getAbsolutePath());
@@ -433,6 +502,13 @@
.thenReturn(path);
}
+ private void setupCustomizationResource(String xml) throws Exception {
+ mSetFlagsRule.enableFlags(
+ Flags.FLAG_LOAD_HAPTIC_FEEDBACK_VIBRATION_CUSTOMIZATION_FROM_RESOURCES);
+ when(mResourcesMock.getXml(haptic_feedback_customization))
+ .thenReturn(FakeXmlResourceParser.fromXml(xml));
+ }
+
private void makeSupported(VibrationEffect... effects) {
for (VibrationEffect effect : effects) {
when(mVibratorInfoMock.areVibrationFeaturesSupported(effect)).thenReturn(true);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 4f75931..240bd1e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -50,7 +50,6 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.VibratorInfo;
-import android.os.vibrator.Flags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.AtomicFile;
import android.util.SparseArray;
@@ -256,22 +255,7 @@
}
@Test
- public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOff_defaultVibrationReturned() {
- mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
- mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
- mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
-
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_TAP))
- .isEqualTo(VibrationEffect.get(EFFECT_CLICK, true /* fallback */));
- assertThat(hapticProvider.getVibrationForHapticFeedback(KEYBOARD_RELEASE))
- .isEqualTo(VibrationEffect.get(EFFECT_TICK, false /* fallback */));
- }
-
- @Test
public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() {
- mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
@@ -346,24 +330,7 @@
}
@Test
- public void testVibrationAttribute_keyboardCategoryOff_isIme_useTouchUsage() {
- mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
- HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
-
- for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
- VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
- effectId, /* flags */ 0,
- HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
- assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
- .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
- assertWithMessage("Expected no CATEGORY_KEYBOARD for effect " + effectId)
- .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN);
- }
- }
-
- @Test
public void testVibrationAttribute_keyboardCategoryOn_notIme_useTouchUsage() {
- mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
@@ -378,7 +345,6 @@
@Test
public void testVibrationAttribute_keyboardCategoryOn_isIme_useImeFeedbackUsage() {
- mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index 9ebeaa8..4704691 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -50,10 +50,9 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
-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.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -71,12 +70,13 @@
import org.mockito.junit.MockitoRule;
public class VibrationScalerTest {
+ private static final float TOLERANCE = 1e-2f;
+ private static final int TEST_DEFAULT_AMPLITUDE = 255;
+ private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private PowerManagerInternal mPowerManagerInternalMock;
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -96,6 +96,10 @@
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
+ when(mVibrationConfigMock.getDefaultVibrationAmplitude())
+ .thenReturn(TEST_DEFAULT_AMPLITUDE);
+ when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain())
+ .thenReturn(TEST_DEFAULT_SCALE_LEVEL_GAIN);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
@@ -107,7 +111,7 @@
mVibrationSettings = new VibrationSettings(
mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
- mVibrationScaler = new VibrationScaler(mContextSpy, mVibrationSettings);
+ mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
mVibrationSettings.onSystemReady();
}
@@ -147,33 +151,76 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
- public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() {
- setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
- setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ @DisableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testGetScaleFactor_withLegacyScaling() {
+ // Default scale gain will be ignored.
+ when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain()).thenReturn(1.4f);
+ mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
+ setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ assertEquals(1.4f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_HIGH
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+ assertEquals(1.2f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // HIGH
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+ assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+
+ setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+ assertEquals(0.8f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // LOW
+
+ setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
+ assertEquals(0.6f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_LOW
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ // Vibration setting being bypassed will use default setting and not scale.
+ assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HAPTICS_SCALE_V2_ENABLED)
+ public void testGetScaleFactor_withScalingV2() {
+ // Test scale factors for a default gain of 1.4
+ when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain()).thenReturn(1.4f);
+ mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
+
+ setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ assertEquals(1.95f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_HIGH
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
+ assertEquals(1.4f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // HIGH
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
+ assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+
+ setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_MEDIUM);
+ assertEquals(0.71f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // LOW
+
+ setDefaultIntensity(USAGE_TOUCH, VIBRATION_INTENSITY_HIGH);
+ assertEquals(0.51f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // VERY_LOW
+
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ // Vibration setting being bypassed will use default setting and not scale.
+ assertEquals(1f, mVibrationScaler.getScaleFactor(USAGE_TOUCH), TOLERANCE); // NONE
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() {
mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
assertEquals(0.5f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_TOUCH));
assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
assertEquals(1f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_NOTIFICATION));
-
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- // Vibration setting being bypassed will apply adaptive haptics scales.
assertEquals(0.2f, mVibrationScaler.getAdaptiveHapticsScale(USAGE_RINGTONE));
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @DisableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void testAdaptiveHapticsScale_flagDisabled_adaptiveHapticScaleAlwaysNone() {
- setDefaultIntensity(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_LOW);
- setDefaultIntensity(USAGE_RINGTONE, Vibrator.VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
-
mVibrationScaler.updateAdaptiveHapticsScale(USAGE_TOUCH, 0.5f);
mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.2f);
@@ -233,7 +280,7 @@
}
@Test
- @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() {
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
@@ -269,13 +316,13 @@
StepSegment resolved = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE),
USAGE_RINGTONE));
- assertTrue(resolved.getAmplitude() > 0);
+ assertEquals(TEST_DEFAULT_AMPLITUDE / 255f, resolved.getAmplitude(), TOLERANCE);
resolved = getFirstSegment(mVibrationScaler.scale(
VibrationEffect.createWaveform(new long[]{10},
new int[]{VibrationEffect.DEFAULT_AMPLITUDE}, -1),
USAGE_RINGTONE));
- assertTrue(resolved.getAmplitude() > 0);
+ assertEquals(TEST_DEFAULT_AMPLITUDE / 255f, resolved.getAmplitude(), TOLERANCE);
}
@Test
@@ -330,7 +377,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void scale_withAdaptiveHaptics_scalesVibrationsCorrectly() {
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
@@ -351,7 +398,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void scale_clearAdaptiveHapticsScales_clearsAllCachedScales() {
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
@@ -373,7 +420,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
public void scale_removeAdaptiveHapticsScale_removesCachedScale() {
setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
@@ -395,7 +442,7 @@
}
@Test
- @RequiresFlagsEnabled({
+ @EnableFlags({
android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
})
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 8d4a6aa..72ef888 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -70,9 +70,7 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
-import android.os.vibrator.Flags;
import android.os.vibrator.VibrationConfig;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
@@ -602,7 +600,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
@@ -627,7 +624,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
@@ -645,7 +641,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_notSupportKeyboardVibration_ignoresKeyboardTouchVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 3bd56de..0fbdce4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -102,6 +102,8 @@
private static final String PACKAGE_NAME = "package";
private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
private static final int TEST_RAMP_STEP_DURATION = 5;
+ private static final int TEST_DEFAULT_AMPLITUDE = 255;
+ private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
@@ -133,6 +135,10 @@
when(mVibrationConfigMock.getDefaultVibrationIntensity(anyInt()))
.thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
when(mVibrationConfigMock.getRampStepDurationMs()).thenReturn(TEST_RAMP_STEP_DURATION);
+ when(mVibrationConfigMock.getDefaultVibrationAmplitude())
+ .thenReturn(TEST_DEFAULT_AMPLITUDE);
+ when(mVibrationConfigMock.getDefaultVibrationScaleLevelGain())
+ .thenReturn(TEST_DEFAULT_SCALE_LEVEL_GAIN);
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
doAnswer(answer -> {
@@ -146,7 +152,7 @@
Context context = InstrumentationRegistry.getContext();
mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
mVibrationConfigMock);
- mVibrationScaler = new VibrationScaler(context, mVibrationSettings);
+ mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
mockVibrators(VIBRATOR_ID);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index c496bbb..79e272b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
@@ -46,6 +47,7 @@
import android.os.Process;
import android.os.test.TestLooper;
import android.os.vibrator.Flags;
+import android.os.vibrator.VibrationConfig;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -97,8 +99,9 @@
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
TestLooper testLooper = new TestLooper();
- mVibrationSettings = new VibrationSettings(
- ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
+ Context context = ApplicationProvider.getApplicationContext();
+ mVibrationSettings = new VibrationSettings(context, new Handler(testLooper.getLooper()),
+ new VibrationConfig(context.getResources()));
mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
mVibratorControlService = new VibratorControlService(
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index e411a17..f009229 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2743,7 +2743,7 @@
}
private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
- int constant, boolean always) throws InterruptedException {
+ int constant, boolean always) throws InterruptedException {
HalVibration vib = service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT,
PACKAGE_NAME, constant, "some reason", service,
always ? HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING : 0 /* flags */,
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
new file mode 100644
index 0000000..ab7d43c
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeXmlResourceParser.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.vibrator;
+
+import android.content.res.XmlResourceParser;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+/**
+ * Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml(). This is borrowed
+ * from {@code ZenModeHelperTest}.
+ */
+public final class FakeXmlResourceParser implements XmlResourceParser {
+ private final TypedXmlPullParser mParser;
+
+ public FakeXmlResourceParser(TypedXmlPullParser parser) {
+ this.mParser = parser;
+ }
+
+ /** Create a {@link FakeXmlResourceParser} given a xml {@link String}. */
+ public static XmlResourceParser fromXml(String xml) throws XmlPullParserException {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null);
+ return new FakeXmlResourceParser(parser);
+ }
+
+ @Override
+ public int getEventType() throws XmlPullParserException {
+ return mParser.getEventType();
+ }
+
+ @Override
+ public void setFeature(String name, boolean state) throws XmlPullParserException {
+ mParser.setFeature(name, state);
+ }
+
+ @Override
+ public boolean getFeature(String name) {
+ return false;
+ }
+
+ @Override
+ public void setProperty(String name, Object value) throws XmlPullParserException {
+ mParser.setProperty(name, value);
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ return mParser.getProperty(name);
+ }
+
+ @Override
+ public void setInput(Reader in) throws XmlPullParserException {
+ mParser.setInput(in);
+ }
+
+ @Override
+ public void setInput(InputStream inputStream, String inputEncoding)
+ throws XmlPullParserException {
+ mParser.setInput(inputStream, inputEncoding);
+ }
+
+ @Override
+ public String getInputEncoding() {
+ return mParser.getInputEncoding();
+ }
+
+ @Override
+ public void defineEntityReplacementText(String entityName, String replacementText)
+ throws XmlPullParserException {
+ mParser.defineEntityReplacementText(entityName, replacementText);
+ }
+
+ @Override
+ public int getNamespaceCount(int depth) throws XmlPullParserException {
+ return mParser.getNamespaceCount(depth);
+ }
+
+ @Override
+ public String getNamespacePrefix(int pos) throws XmlPullParserException {
+ return mParser.getNamespacePrefix(pos);
+ }
+
+ @Override
+ public String getNamespaceUri(int pos) throws XmlPullParserException {
+ return mParser.getNamespaceUri(pos);
+ }
+
+ @Override
+ public String getNamespace(String prefix) {
+ return mParser.getNamespace(prefix);
+ }
+
+ @Override
+ public int getDepth() {
+ return mParser.getDepth();
+ }
+
+ @Override
+ public String getPositionDescription() {
+ return mParser.getPositionDescription();
+ }
+
+ @Override
+ public int getLineNumber() {
+ return mParser.getLineNumber();
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return mParser.getColumnNumber();
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException {
+ return mParser.isWhitespace();
+ }
+
+ @Override
+ public String getText() {
+ return mParser.getText();
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] holderForStartAndLength) {
+ return mParser.getTextCharacters(holderForStartAndLength);
+ }
+
+ @Override
+ public String getNamespace() {
+ return mParser.getNamespace();
+ }
+
+ @Override
+ public String getName() {
+ return mParser.getName();
+ }
+
+ @Override
+ public String getPrefix() {
+ return mParser.getPrefix();
+ }
+
+ @Override
+ public boolean isEmptyElementTag() throws XmlPullParserException {
+ return false;
+ }
+
+ @Override
+ public int getAttributeCount() {
+ return mParser.getAttributeCount();
+ }
+
+ @Override
+ public int next() throws IOException, XmlPullParserException {
+ return mParser.next();
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException {
+ return mParser.next();
+ }
+
+ @Override
+ public void require(int type, String namespace, String name)
+ throws XmlPullParserException, IOException {
+ mParser.require(type, namespace, name);
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException {
+ return mParser.nextText();
+ }
+
+ @Override
+ public String getAttributeNamespace(int index) {
+ return "";
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ return mParser.getAttributeName(index);
+ }
+
+ @Override
+ public String getAttributePrefix(int index) {
+ return mParser.getAttributePrefix(index);
+ }
+
+ @Override
+ public String getAttributeType(int index) {
+ return mParser.getAttributeType(index);
+ }
+
+ @Override
+ public boolean isAttributeDefault(int index) {
+ return mParser.isAttributeDefault(index);
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ return mParser.getAttributeValue(index);
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) {
+ return mParser.getAttributeValue(namespace, name);
+ }
+
+ @Override
+ public int getAttributeNameResource(int index) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeListValue(String namespace, String attribute, String[] options,
+ int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(String namespace, String attribute,
+ boolean defaultValue) {
+ return false;
+ }
+
+ @Override
+ public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(String namespace, String attribute,
+ int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public float getAttributeFloatValue(String namespace, String attribute,
+ float defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeListValue(int index, String[] options, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
+ return false;
+ }
+
+ @Override
+ public int getAttributeResourceValue(int index, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeIntValue(int index, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getAttributeUnsignedIntValue(int index, int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public float getAttributeFloatValue(int index, float defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public String getIdAttribute() {
+ return null;
+ }
+
+ @Override
+ public String getClassAttribute() {
+ return null;
+ }
+
+ @Override
+ public int getIdAttributeResourceValue(int defaultValue) {
+ return 0;
+ }
+
+ @Override
+ public int getStyleAttribute() {
+ return 0;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public int nextTag() throws IOException, XmlPullParserException {
+ return mParser.nextTag();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
new file mode 100644
index 0000000..e26f3e0
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
+import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
+import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
+
+import android.hardware.input.KeyboardSystemShortcut;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.KeyEvent;
+
+import androidx.test.filters.MediumTest;
+
+import com.android.internal.annotations.Keep;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@MediumTest
+@RunWith(JUnitParamsRunner.class)
+public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
+ private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
+ private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
+ private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
+ private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
+ private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
+ private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
+ private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
+
+ @Keep
+ private static Object[][] shortcutTestArguments() {
+ // testName, testKeys, expectedSystemShortcut, expectedKey, expectedModifierState
+ return new Object[][]{
+ {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_H, META_ON},
+ {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_ENTER,
+ META_ON},
+ {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME,
+ KeyEvent.KEYCODE_HOME, 0},
+ {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS,
+ KeyEvent.KEYCODE_RECENT_APPS, 0},
+ {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+ META_ON},
+ {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+ ALT_ON},
+ {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK,
+ KeyEvent.KEYCODE_BACK, 0},
+ {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_ESCAPE,
+ META_ON},
+ {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
+ META_ON},
+ {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DEL, META_ON},
+ {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+ KeyEvent.KEYCODE_APP_SWITCH, 0},
+ {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+ KeyEvent.KEYCODE_ASSIST, 0},
+ {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
+ META_ON},
+ {"VOICE_ASSIST key -> Launch Voice Assistant",
+ new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+ KeyEvent.KEYCODE_VOICE_ASSIST, 0},
+ {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+ KeyEvent.KEYCODE_I, META_ON},
+ {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_N, META_ON},
+ {"NOTIFICATION key -> Toggle Notification Panel",
+ new int[]{KeyEvent.KEYCODE_NOTIFICATION},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_NOTIFICATION,
+ 0},
+ {"Meta + Ctrl + S -> Take Screenshot",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
+ META_ON | CTRL_ON},
+ {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+ KeyEvent.KEYCODE_SLASH, META_ON},
+ {"BRIGHTNESS_UP key -> Increase Brightness",
+ new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+ KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
+ {"BRIGHTNESS_DOWN key -> Decrease Brightness",
+ new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+ KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
+ {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
+ new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
+ {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
+ new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
+ {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
+ new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+ KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
+ {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP,
+ KeyEvent.KEYCODE_VOLUME_UP, 0},
+ {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN,
+ KeyEvent.KEYCODE_VOLUME_DOWN, 0},
+ {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE,
+ KeyEvent.KEYCODE_VOLUME_MUTE, 0},
+ {"ALL_APPS key -> Open App Drawer in Accessibility mode",
+ new int[]{KeyEvent.KEYCODE_ALL_APPS},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+ KeyEvent.KEYCODE_ALL_APPS, 0},
+ {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+ KeyEvent.KEYCODE_SEARCH, 0},
+ {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
+ new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+ KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
+ {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, META_KEY,
+ META_ON},
+ {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, ALT_KEY,
+ META_ON | ALT_ON},
+ {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, META_KEY,
+ META_ON | ALT_ON},
+ {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+ KeyEvent.KEYCODE_CAPS_LOCK, 0},
+ {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
+ 0},
+ {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION,
+ KeyEvent.KEYCODE_DPAD_UP,
+ META_ON | CTRL_ON},
+ {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ META_ON | CTRL_ON},
+ {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ META_ON | CTRL_ON},
+ {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN, KeyEvent.KEYCODE_L,
+ META_ON},
+ {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES, KeyEvent.KEYCODE_N,
+ META_ON | CTRL_ON},
+ {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
+ 0},
+ {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER,
+ KeyEvent.KEYCODE_TV_POWER, 0},
+ {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
+ 0},
+ {"SYSTEM_NAVIGATION_UP key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
+ 0},
+ {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
+ 0},
+ {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
+ new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+ KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
+ {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+ {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
+ 0},
+ {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+ {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+ KeyEvent.KEYCODE_MEDIA_PLAY, 0},
+ {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+ KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
+ {"MEDIA_PLAY_PAUSE key -> Media Control",
+ new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
+ {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+ KeyEvent.KEYCODE_B, META_ON},
+ {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+ KeyEvent.KEYCODE_EXPLORER, 0},
+ {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+ KeyEvent.KEYCODE_C, META_ON},
+ {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+ KeyEvent.KEYCODE_CONTACTS, 0},
+ {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+ KeyEvent.KEYCODE_E, META_ON},
+ {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+ KeyEvent.KEYCODE_ENVELOPE, 0},
+ {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+ KeyEvent.KEYCODE_K, META_ON},
+ {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+ KeyEvent.KEYCODE_CALENDAR, 0},
+ {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+ KeyEvent.KEYCODE_P, META_ON},
+ {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+ KeyEvent.KEYCODE_MUSIC, 0},
+ {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+ KeyEvent.KEYCODE_U, META_ON},
+ {"CALCULATOR key -> Launch Default Calculator",
+ new int[]{KeyEvent.KEYCODE_CALCULATOR},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+ KeyEvent.KEYCODE_CALCULATOR, 0},
+ {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+ KeyEvent.KEYCODE_M, META_ON},
+ {"Meta + S -> Launch Default Messaging App",
+ new int[]{META_KEY, KeyEvent.KEYCODE_S},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+ KeyEvent.KEYCODE_S, META_ON},
+ {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ META_ON | CTRL_ON}};
+ }
+
+ @Keep
+ private static Object[][] longPressOnHomeTestArguments() {
+ // testName, testKeys, longPressOnHomeBehavior, expectedSystemShortcut, expectedKey,
+ // expectedModifierState
+ return new Object[][]{
+ {"Long press HOME key -> Toggle Notification panel",
+ new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_HOME, 0},
+ {"Long press META + ENTER -> Toggle Notification panel",
+ new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+ LONG_PRESS_HOME_NOTIFICATION_PANEL,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_ENTER,
+ META_ON},
+ {"Long press META + H -> Toggle Notification panel",
+ new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_H, META_ON},
+ {"Long press HOME key -> Launch assistant",
+ new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+ KeyEvent.KEYCODE_HOME, 0},
+ {"Long press META + ENTER -> Launch assistant",
+ new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+ KeyEvent.KEYCODE_ENTER, META_ON},
+ {"Long press META + H -> Launch assistant",
+ new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
+ META_ON},
+ {"Long press HOME key -> Open App Drawer in Accessibility mode",
+ new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+ KeyEvent.KEYCODE_HOME, 0},
+ {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
+ new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+ KeyEvent.KEYCODE_ENTER, META_ON},
+ {"Long press META + H -> Open App Drawer in Accessibility mode",
+ new int[]{META_KEY, KeyEvent.KEYCODE_H},
+ LONG_PRESS_HOME_ALL_APPS,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+ KeyEvent.KEYCODE_H, META_ON}};
+ }
+
+ @Keep
+ private static Object[][] doubleTapOnHomeTestArguments() {
+ // testName, testKeys, doubleTapOnHomeBehavior, expectedSystemShortcut, expectedKey,
+ // expectedModifierState
+ return new Object[][]{
+ {"Double tap HOME -> Open App switcher",
+ new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_HOME,
+ 0},
+ {"Double tap META + ENTER -> Open App switcher",
+ new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+ DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+ KeyEvent.KEYCODE_ENTER, META_ON},
+ {"Double tap META + H -> Open App switcher",
+ new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_H,
+ META_ON}};
+ }
+
+ @Keep
+ private static Object[][] settingsKeyTestArguments() {
+ // testName, testKeys, settingsKeyBehavior, expectedSystemShortcut, expectedKey,
+ // expectedModifierState
+ return new Object[][]{
+ {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
+ SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+ KeyEvent.KEYCODE_SETTINGS, 0}};
+ }
+
+ @Before
+ public void setUp() {
+ setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
+ mPhoneWindowManager.overrideLaunchHome();
+ mPhoneWindowManager.overrideSearchKeyBehavior(
+ PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
+ mPhoneWindowManager.overrideEnableBugReportTrigger(true);
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ mPhoneWindowManager.overrideStartActivity();
+ mPhoneWindowManager.overrideSendBroadcast();
+ mPhoneWindowManager.overrideUserSetupComplete();
+ mPhoneWindowManager.setupAssistForLaunch();
+ mPhoneWindowManager.overrideTogglePanel();
+ mPhoneWindowManager.overrideInjectKeyEvent();
+ }
+
+ @Test
+ @Parameters(method = "shortcutTestArguments")
+ public void testShortcut(String testName, int[] testKeys,
+ @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+ int expectedModifierState) {
+ testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+ expectedModifierState);
+ }
+
+ @Test
+ @Parameters(method = "longPressOnHomeTestArguments")
+ public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
+ @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+ int expectedModifierState) {
+ mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
+ sendLongPressKeyCombination(testKeys);
+ mPhoneWindowManager.assertKeyboardShortcutTriggered(
+ new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+ "Failed while executing " + testName);
+ }
+
+ @Test
+ @Parameters(method = "doubleTapOnHomeTestArguments")
+ public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
+ int doubleTapOnHomeBehavior,
+ @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+ int expectedModifierState) {
+ mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
+ sendKeyCombination(testKeys, 0 /* duration */);
+ sendKeyCombination(testKeys, 0 /* duration */);
+ mPhoneWindowManager.assertKeyboardShortcutTriggered(
+ new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+ "Failed while executing " + testName);
+ }
+
+ @Test
+ @Parameters(method = "settingsKeyTestArguments")
+ public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
+ @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+ int expectedModifierState) {
+ mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
+ testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+ expectedModifierState);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+ public void testBugreportShortcutPress() {
+ testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
+ META_ON | CTRL_ON);
+ }
+
+ private void testShortcutInternal(String testName, int[] testKeys,
+ @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+ int expectedModifierState) {
+ sendKeyCombination(testKeys, 0 /* duration */);
+ mPhoneWindowManager.assertKeyboardShortcutTriggered(
+ new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+ "Failed while executing " + testName);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
deleted file mode 100644
index aa28147..0000000
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.policy;
-
-import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECENT_SYSTEM_UI;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST;
-import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
-import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
-
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.view.KeyEvent;
-
-import androidx.test.filters.MediumTest;
-
-import com.android.internal.annotations.Keep;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
-
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@MediumTest
-@RunWith(JUnitParamsRunner.class)
-public class ShortcutLoggingTests extends ShortcutKeyTestBase {
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private static final int VENDOR_ID = 0x123;
- private static final int PRODUCT_ID = 0x456;
- private static final int DEVICE_BUS = 0x789;
- private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
- private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
- private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
- private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
- private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
- private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
- private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
- private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
-
- @Keep
- private static Object[][] shortcutTestArguments() {
- // testName, testKeys, expectedLogEvent, expectedKey, expectedModifierState
- return new Object[][]{
- {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
- KeyboardLogEvent.HOME, KeyEvent.KEYCODE_H, META_ON},
- {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- KeyboardLogEvent.HOME, KeyEvent.KEYCODE_ENTER, META_ON},
- {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, KeyboardLogEvent.HOME,
- KeyEvent.KEYCODE_HOME, 0},
- {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
- KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0},
- {"Meta + Tab -> Open OVerview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
- KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, META_ON},
- {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
- KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
- {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
- KeyEvent.KEYCODE_BACK, 0},
- {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
- KeyboardLogEvent.BACK, KeyEvent.KEYCODE_ESCAPE, META_ON},
- {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
- KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
- {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
- KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
- {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
- KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
- {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
- KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ASSIST, 0},
- {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
- KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, META_ON},
- {"VOICE_ASSIST key -> Launch Voice Assistant",
- new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
- KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT, KeyEvent.KEYCODE_VOICE_ASSIST, 0},
- {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
- KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS, KeyEvent.KEYCODE_I, META_ON},
- {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
- KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_N, META_ON},
- {"NOTIFICATION key -> Toggle Notification Panel",
- new int[]{KeyEvent.KEYCODE_NOTIFICATION},
- KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
- 0},
- {"Meta + Ctrl + S -> Take Screenshot",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
- KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},
- {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
- KeyboardLogEvent.OPEN_SHORTCUT_HELPER, KeyEvent.KEYCODE_SLASH, META_ON},
- {"BRIGHTNESS_UP key -> Increase Brightness",
- new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP}, KeyboardLogEvent.BRIGHTNESS_UP,
- KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
- {"BRIGHTNESS_DOWN key -> Decrease Brightness",
- new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
- KeyboardLogEvent.BRIGHTNESS_DOWN, KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
- {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
- new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
- KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
- {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
- new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
- KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
- {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
- new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
- KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE,
- KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
- {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
- KeyboardLogEvent.VOLUME_UP, KeyEvent.KEYCODE_VOLUME_UP, 0},
- {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
- KeyboardLogEvent.VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN, 0},
- {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
- KeyboardLogEvent.VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0},
- {"ALL_APPS key -> Open App Drawer in Accessibility mode",
- new int[]{KeyEvent.KEYCODE_ALL_APPS},
- KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0},
- {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
- KeyboardLogEvent.LAUNCH_SEARCH, KeyEvent.KEYCODE_SEARCH, 0},
- {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
- new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
- KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
- {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
- KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON},
- {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
- KeyboardLogEvent.TOGGLE_CAPS_LOCK, ALT_KEY, META_ON | ALT_ON},
- {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
- KeyboardLogEvent.TOGGLE_CAPS_LOCK, META_KEY, META_ON | ALT_ON},
- {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
- KeyboardLogEvent.TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0},
- {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
- KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
- {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
- KeyboardLogEvent.MULTI_WINDOW_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
- META_ON | CTRL_ON},
- {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
- KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_LEFT,
- META_ON | CTRL_ON},
- {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
- KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_RIGHT,
- META_ON | CTRL_ON},
- {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
- KeyboardLogEvent.LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON},
- {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
- KeyboardLogEvent.OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON},
- {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
- KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_POWER, 0},
- {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
- KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_TV_POWER, 0},
- {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
- KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
- 0},
- {"SYSTEM_NAVIGATION_UP key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
- KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
- 0},
- {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
- KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
- 0},
- {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
- new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
- KeyboardLogEvent.SYSTEM_NAVIGATION,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
- {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
- KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
- {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
- KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, 0},
- {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
- KeyboardLogEvent.WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
- {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
- KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY, 0},
- {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
- KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
- {"MEDIA_PLAY_PAUSE key -> Media Control",
- new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, KeyboardLogEvent.MEDIA_KEY,
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
- {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
- KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_B, META_ON},
- {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
- KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_EXPLORER, 0},
- {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
- KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_C, META_ON},
- {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
- KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_CONTACTS, 0},
- {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
- KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_E, META_ON},
- {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
- KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_ENVELOPE, 0},
- {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
- KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_K, META_ON},
- {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
- KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_CALENDAR, 0},
- {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
- KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_P, META_ON},
- {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
- KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_MUSIC, 0},
- {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
- KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_U, META_ON},
- {"CALCULATOR key -> Launch Default Calculator",
- new int[]{KeyEvent.KEYCODE_CALCULATOR},
- KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_CALCULATOR, 0},
- {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
- KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
- {"Meta + S -> Launch Default Messaging App",
- new int[]{META_KEY, KeyEvent.KEYCODE_S},
- KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON},
- {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
- new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
- KeyboardLogEvent.DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN,
- META_ON | CTRL_ON}};
- }
-
- @Keep
- private static Object[][] longPressOnHomeTestArguments() {
- // testName, testKeys, longPressOnHomeBehavior, expectedLogEvent, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"Long press HOME key -> Toggle Notification panel",
- new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Toggle Notification panel",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_ENTER,
- META_ON},
- {"Long press META + H -> Toggle Notification panel",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
- KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_H, META_ON},
- {"Long press HOME key -> Launch assistant",
- new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
- KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Launch assistant",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
- KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ENTER, META_ON},
- {"Long press META + H -> Launch assistant",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
- KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, META_ON},
- {"Long press HOME key -> Open App Drawer in Accessibility mode",
- new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
- KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_HOME, 0},
- {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
- KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, KeyEvent.KEYCODE_ENTER, META_ON},
- {"Long press META + H -> Open App Drawer in Accessibility mode",
- new int[]{META_KEY, KeyEvent.KEYCODE_H},
- LONG_PRESS_HOME_ALL_APPS, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS,
- KeyEvent.KEYCODE_H, META_ON}};
- }
-
- @Keep
- private static Object[][] doubleTapOnHomeTestArguments() {
- // testName, testKeys, doubleTapOnHomeBehavior, expectedLogEvent, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"Double tap HOME -> Open App switcher",
- new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_HOME, 0},
- {"Double tap META + ENTER -> Open App switcher",
- new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
- DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, KeyboardLogEvent.APP_SWITCH,
- KeyEvent.KEYCODE_ENTER, META_ON},
- {"Double tap META + H -> Open App switcher",
- new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
- KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_H, META_ON}};
- }
-
- @Keep
- private static Object[][] settingsKeyTestArguments() {
- // testName, testKeys, settingsKeyBehavior, expectedLogEvent, expectedKey,
- // expectedModifierState
- return new Object[][]{
- {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
- SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
- KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_SETTINGS, 0}};
- }
-
- @Before
- public void setUp() {
- setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
- mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID, DEVICE_BUS);
- mPhoneWindowManager.overrideLaunchHome();
- mPhoneWindowManager.overrideSearchKeyBehavior(
- PhoneWindowManager.SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY);
- mPhoneWindowManager.overrideEnableBugReportTrigger(true);
- mPhoneWindowManager.overrideStatusBarManagerInternal();
- mPhoneWindowManager.overrideStartActivity();
- mPhoneWindowManager.overrideSendBroadcast();
- mPhoneWindowManager.overrideUserSetupComplete();
- mPhoneWindowManager.setupAssistForLaunch();
- mPhoneWindowManager.overrideTogglePanel();
- mPhoneWindowManager.overrideInjectKeyEvent();
- }
-
- @Test
- @Parameters(method = "shortcutTestArguments")
- public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
- int expectedKey, int expectedModifierState) {
- sendKeyCombination(testKeys, 0 /* duration */);
- mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, DEVICE_BUS,
- "Failed while executing " + testName);
- }
-
- @Test
- @Parameters(method = "longPressOnHomeTestArguments")
- public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
- KeyboardLogEvent expectedLogEvent, int expectedKey, int expectedModifierState) {
- mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
- sendLongPressKeyCombination(testKeys);
- mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, DEVICE_BUS,
- "Failed while executing " + testName);
- }
-
- @Test
- @Parameters(method = "doubleTapOnHomeTestArguments")
- public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
- int doubleTapOnHomeBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
- int expectedModifierState) {
- mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
- sendKeyCombination(testKeys, 0 /* duration */);
- sendKeyCombination(testKeys, 0 /* duration */);
- mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, DEVICE_BUS,
- "Failed while executing " + testName);
- }
-
- @Test
- @Parameters(method = "settingsKeyTestArguments")
- public void testSettingsKey(String testName, int[] testKeys,
- int settingsKeyBehavior, KeyboardLogEvent expectedLogEvent, int expectedKey,
- int expectedModifierState) {
- mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
- sendKeyCombination(testKeys, 0 /* duration */);
- mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, DEVICE_BUS,
- "Failed while executing " + testName);
- }
-
- @Test
- @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
- public void testBugreportShortcutPress() {
- sendKeyCombination(new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL}, 0);
- mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID,
- KeyboardLogEvent.TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL, META_ON | CTRL_ON,
- DEVICE_BUS, "Failed to log bugreport shortcut.");
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6f8c91c..f9b5c2a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -26,7 +26,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.description;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -50,6 +49,7 @@
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.withSettings;
@@ -70,6 +70,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardSystemShortcut;
import android.media.AudioManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
@@ -85,7 +86,6 @@
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
import android.view.Display;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillManagerInternal;
@@ -93,11 +93,9 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.policy.KeyInterceptionInfo;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
-import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -269,7 +267,6 @@
// Return mocked services: LocalServices.getService
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
- .mockStatic(FrameworkStatsLog.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -583,19 +580,6 @@
doReturn(mPackageManager).when(mContext).getPackageManager();
}
- void overrideKeyEventSource(int vendorId, int productId, int deviceBus) {
- InputDevice device = new InputDevice.Builder()
- .setId(1)
- .setVendorId(vendorId)
- .setProductId(productId)
- .setDeviceBus(deviceBus)
- .setSources(InputDevice.SOURCE_KEYBOARD)
- .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
- .build();
- doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
- doReturn(device).when(mInputManager).getInputDevice(anyInt());
- }
-
void overrideInjectKeyEvent() {
doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt());
}
@@ -820,12 +804,11 @@
Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent());
}
- void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
- int expectedKey, int expectedModifierState, int deviceBus, String errorMsg) {
+ void assertKeyboardShortcutTriggered(int[] keycodes, int modifierState, int systemShortcut,
+ String errorMsg) {
mTestLooper.dispatchAll();
- verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
- vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
- expectedModifierState, deviceBus), description(errorMsg));
+ verify(mInputManagerInternal, description(errorMsg)).notifyKeyboardShortcutTriggered(
+ anyInt(), eq(keycodes), eq(modifierState), eq(systemShortcut));
}
void assertSwitchToTask(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 03d3029..2a53df9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -258,6 +258,6 @@
Surface.ROTATION_0, new Point(100, 100), new Rect() /* contentInsets */,
new Rect() /* letterboxInsets*/, false /* isLowResolution */,
true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
- false /* isTranslucent */, false /* hasImeSurface */);
+ false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index f8cf97e..a745724 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -36,9 +36,12 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.view.Surface;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.server.wm.utils.TestComponentStack;
@@ -74,19 +77,36 @@
private final int mDisplayHeight;
private DisplayContent mDisplayContent;
+ @Nullable
+ private Consumer<ActivityRecord> mOnPostActivityCreation;
+
+ @Nullable
+ private Consumer<DisplayContent> mOnPostDisplayContentCreation;
+
AppCompatActivityRobot(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
- int displayWidth, int displayHeight) {
+ int displayWidth, int displayHeight,
+ @Nullable Consumer<ActivityRecord> onPostActivityCreation,
+ @Nullable Consumer<DisplayContent> onPostDisplayContentCreation) {
mAtm = atm;
mSupervisor = supervisor;
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
mActivityStack = new TestComponentStack<>();
mTaskStack = new TestComponentStack<>();
+ mOnPostActivityCreation = onPostActivityCreation;
+ mOnPostDisplayContentCreation = onPostDisplayContentCreation;
createNewDisplay();
}
AppCompatActivityRobot(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor,
+ int displayWidth, int displayHeight) {
+ this(wm, atm, supervisor, displayWidth, displayHeight, /* onPostActivityCreation */ null,
+ /* onPostDisplayContentCreation */ null);
+ }
+
+ AppCompatActivityRobot(@NonNull WindowManagerService wm,
@NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor) {
this(wm, atm, supervisor, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
}
@@ -96,6 +116,10 @@
/* inNewDisplay */ false);
}
+ void createActivityWithComponentWithoutTask() {
+ createActivityWithComponentInNewTask(/* inNewTask */ false, /* inNewDisplay */ false);
+ }
+
void createActivityWithComponentInNewTask() {
createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ false);
}
@@ -104,7 +128,6 @@
createActivityWithComponentInNewTask(/* inNewTask */ true, /* inNewDisplay */ true);
}
-
void configureTopActivity(float minAspect, float maxAspect, int screenOrientation,
boolean isUnresizable) {
prepareLimitedBounds(mActivityStack.top(), minAspect, maxAspect, screenOrientation,
@@ -130,6 +153,14 @@
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
}
+ void configureTaskBounds(@NonNull Rect taskBounds) {
+ doReturn(taskBounds).when(mTaskStack.top()).getBounds();
+ }
+
+ void configureTopActivityBounds(@NonNull Rect activityBounds) {
+ doReturn(activityBounds).when(mActivityStack.top()).getBounds();
+ }
+
@NonNull
ActivityRecord top() {
return mActivityStack.top();
@@ -169,6 +200,10 @@
.isActivityEligibleForOrientationOverride(eq(mActivityStack.top()));
}
+ void setTopActivityInTransition(boolean inTransition) {
+ doReturn(inTransition).when(mActivityStack.top()).isInTransition();
+ }
+
void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
doReturn(enabled).when(mActivityStack.top().mAppCompatController
.getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
@@ -238,21 +273,20 @@
void createNewDisplay() {
mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayWidth, mDisplayHeight)
.build();
- spyOn(mDisplayContent);
- spyOnAppCompatCameraPolicy();
+ onPostDisplayContentCreation(mDisplayContent);
}
void createNewTask() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent).build();
- pushTask(newTask);
+ mTaskStack.push(newTask);
}
void createNewTaskWithBaseActivity() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setCreateActivity(true)
.setDisplay(mDisplayContent).build();
- pushTask(newTask);
+ mTaskStack.push(newTask);
pushActivity(newTask.getTopNonFinishingActivity());
}
@@ -378,6 +412,34 @@
pushActivity(newActivity);
}
+ /**
+ * Specific Robots can override this method to add operation to run on a newly created
+ * {@link ActivityRecord}. Common case is to invoke spyOn().
+ *
+ * @param activity The newly created {@link ActivityRecord}.
+ */
+ @CallSuper
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ spyOn(activity.mLetterboxUiController);
+ if (mOnPostActivityCreation != null) {
+ mOnPostActivityCreation.accept(activity);
+ }
+ }
+
+ /**
+ * Specific Robots can override this method to add operation to run on a newly created
+ * {@link DisplayContent}. Common case is to invoke spyOn().
+ *
+ * @param displayContent The newly created {@link DisplayContent}.
+ */
+ @CallSuper
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ spyOn(mDisplayContent);
+ if (mOnPostDisplayContentCreation != null) {
+ mOnPostDisplayContentCreation.accept(mDisplayContent);
+ }
+ }
+
private void createActivityWithComponentInNewTask(boolean inNewTask, boolean inNewDisplay) {
if (inNewDisplay) {
createNewDisplay();
@@ -385,14 +447,16 @@
if (inNewTask) {
createNewTask();
}
- final ActivityRecord activity = new WindowTestsBase.ActivityBuilder(mAtm)
- .setOnTop(true)
- .setTask(mTaskStack.top())
+ final WindowTestsBase.ActivityBuilder activityBuilder =
+ new WindowTestsBase.ActivityBuilder(mAtm).setOnTop(true)
// Set the component to be that of the test class in order
// to enable compat changes
- .setComponent(ComponentName.createRelative(mAtm.mContext, TEST_COMPONENT_NAME))
- .build();
- pushActivity(activity);
+ .setComponent(ComponentName.createRelative(mAtm.mContext, TEST_COMPONENT_NAME));
+ if (!mTaskStack.isEmpty()) {
+ // We put the Activity in the current task if any.
+ activityBuilder.setTask(mTaskStack.top());
+ }
+ pushActivity(activityBuilder.build());
}
/**
@@ -438,28 +502,6 @@
// We add the activity to the stack and spyOn() on its properties.
private void pushActivity(@NonNull ActivityRecord activity) {
mActivityStack.push(activity);
- spyOn(activity);
- // TODO (b/351763164): Use these spyOn calls only when necessary.
- spyOn(activity.mAppCompatController.getTransparentPolicy());
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
- spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
- spyOn(activity.mAppCompatController.getAppCompatFocusOverrides());
- spyOn(activity.mAppCompatController.getAppCompatResizeOverrides());
- spyOn(activity.mLetterboxUiController);
- }
-
- private void pushTask(@NonNull Task task) {
- spyOn(task);
- mTaskStack.push(task);
- }
-
- private void spyOnAppCompatCameraPolicy() {
- spyOn(mDisplayContent.mAppCompatCameraPolicy);
- if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
- spyOn(mDisplayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
- }
- if (mDisplayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
- spyOn(mDisplayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
- }
+ onPostActivityCreation(activity);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index a6fd112..1e40aa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -291,7 +291,6 @@
* Runs a test scenario providing a Robot.
*/
void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) {
- spyOn(mWm.mAppCompatConfiguration);
final AspectRatioOverridesRobotTest robot =
new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor);
consumer.accept(robot);
@@ -305,6 +304,18 @@
super(wm, atm, supervisor);
}
+ @Override
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ super.onPostDisplayContentCreation(displayContent);
+ spyOn(displayContent.mAppCompatCameraPolicy);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ }
+
void checkShouldApplyUserFullscreenOverride(boolean expected) {
assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides()
.shouldApplyUserFullscreenOverride());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index de99f54..84ffcb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -387,6 +387,12 @@
super(wm, atm, supervisor);
}
+ @Override
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ super.onPostDisplayContentCreation(displayContent);
+ spyOn(displayContent.mAppCompatCameraPolicy);
+ }
+
void checkShouldRefreshActivityForCameraCompat(boolean expected) {
Assert.assertEquals(getAppCompatCameraOverrides()
.shouldRefreshActivityForCameraCompat(), expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index 0b1bb0f..c42228d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -150,6 +150,12 @@
super(wm, atm, supervisor);
}
+ @Override
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ super.onPostDisplayContentCreation(displayContent);
+ spyOn(displayContent.mAppCompatCameraPolicy);
+ }
+
void checkTopActivityHasDisplayRotationCompatPolicy(boolean exists) {
Assert.assertEquals(exists, activity().top().mDisplayContent
.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 6592f26..40a5347 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -19,6 +19,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import androidx.annotation.NonNull;
@@ -80,4 +83,34 @@
doReturn(aspectRatio).when(mAppCompatConfiguration)
.getFixedOrientationLetterboxAspectRatio();
}
+
+ void setThinLetterboxWidthPx(int thinWidthPx) {
+ doReturn(thinWidthPx).when(mAppCompatConfiguration)
+ .getThinLetterboxWidthPx();
+ }
+
+ void setThinLetterboxHeightPx(int thinHeightPx) {
+ doReturn(thinHeightPx).when(mAppCompatConfiguration)
+ .getThinLetterboxHeightPx();
+ }
+
+ void checkToNextLeftStop(boolean invoked) {
+ verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+ .movePositionForHorizontalReachabilityToNextLeftStop(anyBoolean());
+ }
+
+ void checkToNextRightStop(boolean invoked) {
+ verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+ .movePositionForHorizontalReachabilityToNextRightStop(anyBoolean());
+ }
+
+ void checkToNextBottomStop(boolean invoked) {
+ verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+ .movePositionForVerticalReachabilityToNextBottomStop(anyBoolean());
+ }
+
+ void checkToNextTopStop(boolean invoked) {
+ verify(mAppCompatConfiguration, times(invoked ? 1 : 0))
+ .movePositionForVerticalReachabilityToNextTopStop(anyBoolean());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 6c0d8c4..d9b5f37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -250,6 +250,12 @@
mTestCurrentTimeMillisSupplier = new CurrentTimeMillisSupplierFake();
}
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+ }
+
// Useful to reduce timeout during tests
void prepareMockedTime() {
getTopOrientationOverrides().mOrientationOverridesState.mCurrentTimeMillisSupplier =
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index ad34a6b..f6d0744 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -536,6 +536,25 @@
}
}
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+ }
+
+ @Override
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ super.onPostDisplayContentCreation(displayContent);
+ spyOn(displayContent.mAppCompatCameraPolicy);
+ if (displayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mDisplayRotationCompatPolicy);
+ }
+ if (displayContent.mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()) {
+ spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy);
+ }
+ }
+
void prepareRelaunchingAfterRequestedOrientationChanged(boolean enabled) {
getTopOrientationOverrides().setRelaunchingAfterRequestedOrientationChanged(enabled);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
new file mode 100644
index 0000000..5ff8f02
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import com.android.window.flags.Flags;
+
+import junit.framework.Assert;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Test class for {@link AppCompatReachabilityOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatReachabilityOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatReachabilityOverridesTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ public void testIsThinLetterboxed_NegativePx_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentWithoutTask();
+ robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ -1);
+ robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+
+ robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ -1);
+ robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testIsThinLetterboxed_noTask_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentWithoutTask();
+ robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ 10);
+ robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+
+ robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ 10);
+ robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+ });
+ }
+
+ @Test
+ public void testIsVerticalThinLetterboxed() {
+ runTestScenario((robot) -> {
+ robot.conf().setThinLetterboxHeightPx(/* thinHeightPx */ 10);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.configureTaskBounds(new Rect(0, 0, 100, 100));
+
+ // (task.width() - act.width()) / 2 = 5 < 10
+ a.configureTopActivityBounds(new Rect(5, 5, 95, 95));
+ robot.checkIsVerticalThinLetterboxed(/* expected */ true);
+
+ // (task.width() - act.width()) / 2 = 10 = 10
+ a.configureTopActivityBounds(new Rect(10, 10, 90, 90));
+ robot.checkIsVerticalThinLetterboxed(/* expected */ true);
+
+ // (task.width() - act.width()) / 2 = 11 > 10
+ a.configureTopActivityBounds(new Rect(11, 11, 89, 89));
+ robot.checkIsVerticalThinLetterboxed(/* expected */ false);
+ });
+ });
+ }
+
+ @Test
+ public void testIsHorizontalThinLetterboxed() {
+ runTestScenario((robot) -> {
+ robot.conf().setThinLetterboxWidthPx(/* thinHeightPx */ 10);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.configureTaskBounds(new Rect(0, 0, 100, 100));
+
+ // (task.height() - act.height()) / 2 = 5 < 10
+ a.configureTopActivityBounds(new Rect(5, 5, 95, 95));
+ robot.checkIsHorizontalThinLetterboxed(/* expected */ true);
+
+ // (task.height() - act.height()) / 2 = 10 = 10
+ a.configureTopActivityBounds(new Rect(10, 10, 90, 90));
+ robot.checkIsHorizontalThinLetterboxed(/* expected */ true);
+
+ // (task.height() - act.height()) / 2 = 11 > 10
+ a.configureTopActivityBounds(new Rect(11, 11, 89, 89));
+ robot.checkIsHorizontalThinLetterboxed(/* expected */ false);
+ });
+ });
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+ public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
+ robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ false);
+ robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
+ robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ false);
+
+ robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
+ robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+ robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
+ robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+ });
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
+ public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+
+ robot.configureIsVerticalThinLetterboxed(/* isThin */ true);
+ robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+ robot.configureIsHorizontalThinLetterboxed(/* isThin */ true);
+ robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+
+ robot.configureIsVerticalThinLetterboxed(/* isThin */ false);
+ robot.checkAllowVerticalReachabilityForThinLetterbox(/* expected */ true);
+ robot.configureIsHorizontalThinLetterboxed(/* isThin */ false);
+ robot.checkAllowHorizontalReachabilityForThinLetterbox(/* expected */ true);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<ReachabilityOverridesRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final ReachabilityOverridesRobotTest robot =
+ new ReachabilityOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class ReachabilityOverridesRobotTest extends AppCompatRobotBase {
+
+ private final Supplier<Rect> mLetterboxInnerBoundsSupplier = spy(Rect::new);
+
+ ReachabilityOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
+ activity.mAppCompatController.getAppCompatReachabilityPolicy()
+ .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
+ }
+
+ void configureIsVerticalThinLetterboxed(boolean isThin) {
+ doReturn(isThin).when(getAppCompatReachabilityOverrides())
+ .isVerticalThinLetterboxed();
+ }
+
+ void configureIsHorizontalThinLetterboxed(boolean isThin) {
+ doReturn(isThin).when(getAppCompatReachabilityOverrides())
+ .isHorizontalThinLetterboxed();
+ }
+
+ void checkIsVerticalThinLetterboxed(boolean expected) {
+ Assert.assertEquals(expected,
+ getAppCompatReachabilityOverrides().isVerticalThinLetterboxed());
+ }
+
+ void checkIsHorizontalThinLetterboxed(boolean expected) {
+ Assert.assertEquals(expected,
+ getAppCompatReachabilityOverrides().isHorizontalThinLetterboxed());
+ }
+
+ void checkAllowVerticalReachabilityForThinLetterbox(boolean expected) {
+ Assert.assertEquals(expected, getAppCompatReachabilityOverrides()
+ .allowVerticalReachabilityForThinLetterbox());
+ }
+
+ void checkAllowHorizontalReachabilityForThinLetterbox(boolean expected) {
+ Assert.assertEquals(expected, getAppCompatReachabilityOverrides()
+ .allowHorizontalReachabilityForThinLetterbox());
+ }
+
+ @NonNull
+ private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+ return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+ }
+
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
new file mode 100644
index 0000000..96734b3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Test class for {@link AppCompatReachabilityPolicy}.
+ * <p/>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatReachabilityPolicyTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatReachabilityPolicyTest extends WindowTestsBase {
+
+ @Test
+ public void handleHorizontalDoubleTap_reachabilityDisabled_nothingHappen() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableHorizontalReachability(/* enabled */ false);
+ robot.activity().setTopActivityInTransition(/* inTransition */ true);
+ robot.doubleTapAt(100, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+ });
+ }
+
+ @Test
+ public void handleHorizontalDoubleTap_reachabilityEnabledInTransition_nothingHappen() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableHorizontalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ true);
+ robot.doubleTapAt(100, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+ });
+ }
+
+ @Test
+ public void handleHorizontalDoubleTap_reachabilityDisabledNotInTransition_nothingHappen() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableHorizontalReachability(/* enabled */ false);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+ robot.doubleTapAt(100, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+ });
+ }
+
+ @Test
+ public void handleHorizontalDoubleTap_leftInnerFrame_moveToLeft() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableHorizontalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+ robot.doubleTapAt(99, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+ robot.applyOnConf((c) -> {
+ c.checkToNextLeftStop(/* invoked */ true);
+ c.checkToNextRightStop(/* invoked */ false);
+ });
+ });
+ }
+
+ @Test
+ public void handleHorizontalDoubleTap_rightInnerFrame_moveToRight() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableHorizontalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+ robot.doubleTapAt(201, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+ robot.applyOnConf((c) -> {
+ c.checkToNextLeftStop(/* invoked */ false);
+ c.checkToNextRightStop(/* invoked */ true);
+ });
+ });
+ }
+
+ @Test
+ public void handleHorizontalDoubleTap_intoInnerFrame_noMove() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableHorizontalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.configureLetterboxInnerFrameWidth(/* left */ 100, /* right */ 200);
+ robot.doubleTapAt(150, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+ robot.applyOnConf((c) -> {
+ c.checkToNextLeftStop(/* invoked */ false);
+ c.checkToNextRightStop(/* invoked */ false);
+ });
+ });
+ }
+
+
+ @Test
+ public void handleVerticalDoubleTap_reachabilityDisabled_nothingHappen() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableVerticalReachability(/* enabled */ false);
+ robot.activity().setTopActivityInTransition(/* inTransition */ true);
+ robot.doubleTapAt(100, 100);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+ });
+ }
+
+ @Test
+ public void handleVerticalDoubleTap_reachabilityEnabledInTransition_nothingHappen() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableVerticalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ true);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+ });
+ }
+
+ @Test
+ public void handleVerticalDoubleTap_reachabilityDisabledNotInTransition_nothingHappen() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableVerticalReachability(/* enabled */ false);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ false);
+ });
+ }
+
+ @Test
+ public void handleVerticalDoubleTap_topInnerFrame_moveToTop() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableVerticalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+ robot.doubleTapAt(100, 99);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+ robot.applyOnConf((c) -> {
+ c.checkToNextTopStop(/* invoked */ true);
+ c.checkToNextBottomStop(/* invoked */ false);
+ });
+ });
+ }
+
+ @Test
+ public void handleVerticalDoubleTap_bottomInnerFrame_moveToBottom() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableVerticalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+ robot.doubleTapAt(100, 201);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+ robot.applyOnConf((c) -> {
+ c.checkToNextTopStop(/* invoked */ false);
+ c.checkToNextBottomStop(/* invoked */ true);
+ });
+ });
+ }
+
+ @Test
+ public void handleVerticalDoubleTap_intoInnerFrame_noMove() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.enableVerticalReachability(/* enabled */ true);
+ robot.activity().setTopActivityInTransition(/* inTransition */ false);
+
+ robot.configureLetterboxInnerFrameHeight(/* top */ 100, /* bottom */ 200);
+ robot.doubleTapAt(100, 150);
+
+ robot.checkLetterboxInnerFrameProvidedInvoked(/* invoked */ true);
+ robot.applyOnConf((c) -> {
+ c.checkToNextTopStop(/* invoked */ false);
+ c.checkToNextBottomStop(/* invoked */ false);
+ });
+ });
+ }
+
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<ReachabilityPolicyRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final ReachabilityPolicyRobotTest robot =
+ new ReachabilityPolicyRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class ReachabilityPolicyRobotTest extends AppCompatRobotBase {
+
+ private final Supplier<Rect> mLetterboxInnerBoundsSupplier = spy(Rect::new);
+
+ ReachabilityPolicyRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
+ activity.mAppCompatController.getAppCompatReachabilityPolicy()
+ .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
+ }
+
+ void configureLetterboxInnerFrameWidth(int left, int right) {
+ doReturn(new Rect(left, /* top */ 0, right, /* bottom */ 100))
+ .when(mLetterboxInnerBoundsSupplier).get();
+ }
+
+ void configureLetterboxInnerFrameHeight(int top, int bottom) {
+ doReturn(new Rect(/* left */ 0, top, /* right */ 100, bottom))
+ .when(mLetterboxInnerBoundsSupplier).get();
+ }
+
+ void enableHorizontalReachability(boolean enabled) {
+ doReturn(enabled).when(getAppCompatReachabilityOverrides())
+ .isHorizontalReachabilityEnabled();
+ }
+
+ void enableVerticalReachability(boolean enabled) {
+ doReturn(enabled).when(getAppCompatReachabilityOverrides())
+ .isVerticalReachabilityEnabled();
+ }
+
+ void doubleTapAt(int x, int y) {
+ getAppCompatReachabilityPolicy().handleDoubleTap(x, y);
+ }
+
+ void checkLetterboxInnerFrameProvidedInvoked(boolean invoked) {
+ verify(mLetterboxInnerBoundsSupplier, times(invoked ? 1 : 0)).get();
+ }
+
+ @NonNull
+ private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+ return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+ }
+
+ @NonNull
+ private AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
+ return activity().top().mAppCompatController.getAppCompatReachabilityPolicy();
+ }
+
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
index 8fc1a77..cade213 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -39,7 +39,7 @@
/**
* Test class for {@link AppCompatResizeOverrides}.
- * <p>
+ * <p/>
* Build/Install/Run:
* atest WmTests:AppCompatResizeOverridesTest
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
index 6939f97..4e58e1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatRobotBase.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
@@ -42,7 +43,8 @@
@NonNull ActivityTaskSupervisor supervisor,
int displayWidth, int displayHeight) {
mActivityRobot = new AppCompatActivityRobot(wm, atm, supervisor,
- displayWidth, displayHeight);
+ displayWidth, displayHeight, this::onPostActivityCreation,
+ this::onPostDisplayContentCreation);
mConfigurationRobot =
new AppCompatConfigurationRobot(wm.mAppCompatConfiguration);
mOptPropRobot = new AppCompatComponentPropRobot(wm);
@@ -54,6 +56,26 @@
this(wm, atm, supervisor, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
}
+ /**
+ * Specific Robots can override this method to add operation to run on a newly created
+ * {@link ActivityRecord}. Common case is to invoke spyOn().
+ *
+ * @param activity THe newly created {@link ActivityRecord}.
+ */
+ @CallSuper
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ }
+
+ /**
+ * Specific Robots can override this method to add operation to run on a newly created
+ * {@link DisplayContent}. Common case is to invoke spyOn().
+ *
+ * @param displayContent THe newly created {@link DisplayContent}.
+ */
+ @CallSuper
+ void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) {
+ }
+
@NonNull
AppCompatConfigurationRobot conf() {
return mConfigurationRobot;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
index 3cfbb9e..5af7093 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatTransparentActivityRobot.java
@@ -62,6 +62,10 @@
consumer.accept(mActivityRobot);
}
+ void setDisplayContentBounds(int left, int top, int right, int bottom) {
+ mActivityRobot.displayContent().setBounds(left, top, right, bottom);
+ }
+
void launchTransparentActivity() {
mActivityRobot.launchActivity(/*minAspectRatio */ -1, /* maxAspectRatio */ -1,
SCREEN_ORIENTATION_PORTRAIT, /* transparent */ true,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 9e242ee..21fac9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.mockito.Mockito.when;
import android.platform.test.annotations.Presubmit;
@@ -42,7 +44,10 @@
@Test
public void getLetterboxReasonString_inSizeCompatMode() {
runTestScenario((robot) -> {
- robot.activity().setTopActivityInSizeCompatMode(/* inScm */ true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInSizeCompatMode(/* inScm */ true);
+ });
robot.checkTopActivityLetterboxReason(/* expected */ "SIZE_COMPAT_MODE");
});
@@ -51,7 +56,10 @@
@Test
public void getLetterboxReasonString_fixedOrientation() {
runTestScenario((robot) -> {
- robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
/* forFixedOrientationAndAspectRatio */ true);
@@ -62,7 +70,10 @@
@Test
public void getLetterboxReasonString_isLetterboxedForDisplayCutout() {
runTestScenario((robot) -> {
- robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
/* forFixedOrientationAndAspectRatio */ false);
robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ true);
@@ -74,7 +85,10 @@
@Test
public void getLetterboxReasonString_aspectRatio() {
runTestScenario((robot) -> {
- robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
/* forFixedOrientationAndAspectRatio */ false);
robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
@@ -87,7 +101,10 @@
@Test
public void getLetterboxReasonString_unknownReason() {
runTestScenario((robot) -> {
- robot.activity().checkTopActivityInSizeCompatMode(/* inScm */ false);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
/* forFixedOrientationAndAspectRatio */ false);
robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
@@ -97,7 +114,6 @@
});
}
-
/**
* Runs a test scenario providing a Robot.
*/
@@ -114,10 +130,15 @@
@NonNull ActivityTaskManagerService atm,
@NonNull ActivityTaskSupervisor supervisor) {
super(wm, atm, supervisor);
- activity().createActivityWithComponent();
mWindowState = Mockito.mock(WindowState.class);
}
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+ }
+
void setIsLetterboxedForFixedOrientationAndAspectRatio(
boolean forFixedOrientationAndAspectRatio) {
when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index b687042..07e95d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -31,6 +31,7 @@
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
@@ -231,6 +232,56 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultLandscapeBounds_landscapeDevice_userFullscreenOverride() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isUserFullscreenOverrideEnabled();
+
+ final int desiredWidth =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultLandscapeBounds_landscapeDevice_systemFullscreenOverride() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+ LANDSCAPE_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isSystemOverrideToFullscreenEnabled();
+
+ final int desiredWidth =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
setupDesktopModeLaunchParamsModifier();
@@ -332,6 +383,56 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultPortraitBounds_portraitDevice_userFullscreenOverride() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isUserFullscreenOverrideEnabled();
+
+ final int desiredWidth =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS})
+ public void testDefaultPortraitBounds_portraitDevice_systemFullscreenOverride() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+ PORTRAIT_DISPLAY_BOUNDS);
+ final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+
+ spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+ doReturn(true).when(
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+ .isSystemOverrideToFullscreenEnabled();
+
+ final int desiredWidth =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int desiredHeight =
+ (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+ assertEquals(desiredWidth, mResult.mBounds.width());
+ assertEquals(desiredHeight, mResult.mBounds.height());
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
setupDesktopModeLaunchParamsModifier();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index e2524a2..ddadbc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -115,6 +115,17 @@
}
@Test
+ public void testPrimaryDisplayUnchanged_whenWindowingModeAlreadySet_NoFreeformSupport() {
+ mPrimaryDisplay.getDefaultTaskDisplayArea().setWindowingMode(
+ WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+
+ mDisplayWindowSettings.applySettingsToDisplayLocked(mPrimaryDisplay);
+
+ assertEquals(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW,
+ mPrimaryDisplay.getDefaultTaskDisplayArea().getWindowingMode());
+ }
+
+ @Test
public void testPrimaryDisplayDefaultToFullscreen_HasFreeformSupport_NonPc_NoDesktopMode() {
mWm.mAtmService.mSupportsFreeformWindowManagement = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 4a9d5c7..f339d29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -218,16 +218,6 @@
}
@Test
- public void testPerformDrag_NullDataToOtherUser() {
- final WindowState otherUsersWindow =
- createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
- doReturn(otherUsersWindow).when(mDisplayContent).getTouchableWinAtPointLocked(10, 10);
-
- doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 10, 10);
- mToken = otherUsersWindow.mClient.asBinder();
- }
-
- @Test
public void testPrivateInterceptGlobalDragDropFlagChecksPermission() {
DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index e77c14a..eacb8e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -82,12 +82,15 @@
public void setUp() {
DisplayInfo di = new DisplayInfo(mDisplayInfo);
Mode defaultMode = di.getDefaultMode();
- di.supportedModes = new Mode[] {
- new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90),
- new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70),
- new Mode(LOW_MODE_ID,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
- };
+ Mode hiMode = new Mode(1,
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90);
+ Mode midMode = new Mode(2,
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70);
+ Mode lowMode = new Mode(LOW_MODE_ID,
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60);
+
+ di.supportedModes = new Mode[] { hiMode, midMode };
+ di.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
di.defaultModeId = 1;
mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 33df5d8..695068a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -23,11 +23,9 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -36,8 +34,6 @@
import android.content.ComponentName;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -49,7 +45,6 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Rule;
@@ -296,106 +291,6 @@
}
@Test
- public void testIsVerticalThinLetterboxed() {
- // Vertical thin letterbox disabled
- doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
- .getThinLetterboxHeightPx();
- final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
- .getAppCompatReachabilityOverrides();
- assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
- // Define a Task 100x100
- final Task task = mock(Task.class);
- doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
- doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration)
- .getThinLetterboxHeightPx();
-
- // Vertical thin letterbox disabled without Task
- doReturn(null).when(mActivity).getTask();
- assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
- // Assign a Task for the Activity
- doReturn(task).when(mActivity).getTask();
-
- // (task.width() - act.width()) / 2 = 5 < 10
- doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
- assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
-
- // (task.width() - act.width()) / 2 = 10 = 10
- doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
- assertTrue(reachabilityOverrides.isVerticalThinLetterboxed());
-
- // (task.width() - act.width()) / 2 = 11 > 10
- doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
- assertFalse(reachabilityOverrides.isVerticalThinLetterboxed());
- }
-
- @Test
- public void testIsHorizontalThinLetterboxed() {
- // Horizontal thin letterbox disabled
- doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
- .getThinLetterboxWidthPx();
- final AppCompatReachabilityOverrides reachabilityOverrides = mActivity.mAppCompatController
- .getAppCompatReachabilityOverrides();
- assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
- // Define a Task 100x100
- final Task task = mock(Task.class);
- doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds();
- doReturn(10).when(mActivity.mWmService.mAppCompatConfiguration)
- .getThinLetterboxWidthPx();
-
- // Vertical thin letterbox disabled without Task
- doReturn(null).when(mActivity).getTask();
- assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
- // Assign a Task for the Activity
- doReturn(task).when(mActivity).getTask();
-
- // (task.height() - act.height()) / 2 = 5 < 10
- doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds();
- assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
-
- // (task.height() - act.height()) / 2 = 10 = 10
- doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
- assertTrue(reachabilityOverrides.isHorizontalThinLetterboxed());
-
- // (task.height() - act.height()) / 2 = 11 > 10
- doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
- assertFalse(reachabilityOverrides.isHorizontalThinLetterboxed());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
- public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
- final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
- spyOn(reachabilityOverrides);
- doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
- assertFalse(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
- doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
- assertFalse(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-
- doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
- assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
- doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
- assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
- public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
- final AppCompatReachabilityOverrides reachabilityOverrides =
- mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
- spyOn(reachabilityOverrides);
- doReturn(true).when(reachabilityOverrides).isVerticalThinLetterboxed();
- assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
- doReturn(true).when(reachabilityOverrides).isHorizontalThinLetterboxed();
- assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
-
- doReturn(false).when(reachabilityOverrides).isVerticalThinLetterboxed();
- assertTrue(reachabilityOverrides.allowVerticalReachabilityForThinLetterbox());
- doReturn(false).when(reachabilityOverrides).isHorizontalThinLetterboxed();
- assertTrue(reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox());
- }
-
- @Test
public void testIsLetterboxEducationEnabled() {
mController.isLetterboxEducationEnabled();
verify(mAppCompatConfiguration).getIsEducationEnabled();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 33f7035..b95f621 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1413,7 +1413,7 @@
Surface.ROTATION_0, taskSize, new Rect() /* contentInsets */,
new Rect() /* letterboxInsets*/, false /* isLowResolution */,
true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
- false /* isTranslucent */, false /* hasImeSurface */);
+ false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */);
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 7ebf9ac..3fa38bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -66,7 +66,6 @@
private RefreshRatePolicy mPolicy;
private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
- private FrameRateVote mTempFrameRateVote = new FrameRateVote();
private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
@@ -98,18 +97,14 @@
@Before
public void setUp() {
Mode defaultMode = mDisplayInfo.getDefaultMode();
- mDisplayInfo.supportedModes = new Mode[] {
- new Mode(HI_MODE_ID,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
- HI_REFRESH_RATE),
- new Mode(MID_MODE_ID,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
- MID_REFRESH_RATE),
- new Mode(LOW_MODE_ID,
- defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(),
- LOW_REFRESH_RATE),
- };
- mDisplayInfo.appsSupportedModes = mDisplayInfo.supportedModes;
+ Mode hiMode = new Mode(HI_MODE_ID,
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), HI_REFRESH_RATE);
+ Mode midMode = new Mode(MID_MODE_ID,
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), MID_REFRESH_RATE);
+ Mode lowMode = new Mode(LOW_MODE_ID,
+ defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), LOW_REFRESH_RATE);
+ mDisplayInfo.supportedModes = new Mode[] { hiMode, midMode };
+ mDisplayInfo.appsSupportedModes = new Mode[] { hiMode, midMode, lowMode };
mDisplayInfo.defaultModeId = HI_MODE_ID;
mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index b92af87..1e39f0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -139,7 +139,6 @@
private ActivityTaskManagerService mAtmService;
private WindowManagerService mWmService;
private InputManagerService mImService;
- private InputChannel mInputChannel;
private Runnable mOnBeforeServicesCreated;
/**
* Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
@@ -326,12 +325,15 @@
// InputManagerService
mImService = mock(InputManagerService.class);
- // InputChannel cannot be mocked because it may pass to InputEventReceiver.
- final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
- inputChannels[0].dispose();
- mInputChannel = inputChannels[1];
- doReturn(mInputChannel).when(mImService).monitorInput(anyString(), anyInt());
- doReturn(mInputChannel).when(mImService).createInputChannel(anyString());
+ // InputChannel cannot be mocked because it may be passed to InputEventReceiver.
+ Answer<InputChannel> newInputChannel = invocation -> {
+ String name = invocation.getArgument(0);
+ final InputChannel[] channels = InputChannel.openInputChannelPair(name);
+ channels[0].dispose();
+ return channels[1];
+ };
+ when(mImService.monitorInput(anyString(), anyInt())).thenAnswer(newInputChannel);
+ when(mImService.createInputChannel(anyString())).thenAnswer(newInputChannel);
// StatusBarManagerInternal
final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
@@ -464,9 +466,6 @@
SurfaceAnimationThread.dispose();
AnimationThread.dispose();
UiThread.dispose();
- if (mInputChannel != null) {
- mInputChannel.dispose();
- }
tearDownLocalServices();
// Reset priority booster because animation thread has been changed.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index c53addc..6fd5faf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -131,6 +131,7 @@
final Task adjacentRootTask = createTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
+ createActivityRecord(adjacentRootTask);
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
adjacentRootTask.setAdjacentTaskFragment(rootTask);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 84c0696..1e0cef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -227,7 +227,7 @@
// disk.
false /* isLowResolution */,
mIsRealSnapshot, mWindowingMode, mSystemUiVisibility, mIsTranslucent,
- false /* hasImeSurface */);
+ false /* hasImeSurface */, 0 /* uiMode */);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 4b0668f..d62c626 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -270,12 +270,6 @@
}
@Override
- public boolean performHapticFeedback(int uid, String packageName, int effectId, String reason,
- int flags, int privFlags) {
- return false;
- }
-
- @Override
public void keepScreenOnStartedLw() {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index cbf17c4..a0641cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -22,8 +22,11 @@
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.mockito.Mockito.clearInvocations;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
@@ -207,6 +210,25 @@
});
}
+ @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
+ @Test
+ public void testNotRunStrategyToTranslucentActivitiesIfRespectOrientation() {
+ runTestScenario(robot -> robot.transparentActivity(ta -> ta.applyOnActivity((a) -> {
+ a.configureTopActivityIgnoreOrientationRequest(false);
+ // The translucent activity is SCREEN_ORIENTATION_PORTRAIT.
+ ta.launchTransparentActivityInTask();
+ // Though TransparentPolicyState will be started, it won't be considered as running.
+ ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ false);
+
+ // If the display changes to ignore orientation request, e.g. unfold, the policy should
+ // take effect.
+ a.configureTopActivityIgnoreOrientationRequest(true);
+ ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ true);
+ ta.setDisplayContentBounds(0, 0, 900, 1800);
+ ta.checkTopActivityHasInheritedBoundsFrom(/* fromTop */ 1);
+ })), /* displayWidth */ 500, /* displayHeight */ 1000);
+ }
+
@Test
public void testTranslucentActivitiesDontGoInSizeCompatMode() {
runTestScenario((robot) -> {
@@ -343,6 +365,12 @@
activity().createNewTaskWithBaseActivity();
}
+ @Override
+ void onPostActivityCreation(@NonNull ActivityRecord activity) {
+ super.onPostActivityCreation(activity);
+ spyOn(activity.mAppCompatController.getTransparentPolicy());
+ }
+
void transparentActivity(@NonNull Consumer<AppCompatTransparentActivityRobot> consumer) {
consumer.accept(mTransparentActivityRobot);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 11df331..39276a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -77,8 +77,10 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import android.content.res.CompatibilityInfo;
@@ -111,6 +113,7 @@
import androidx.test.filters.SmallTest;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
@@ -1337,8 +1340,8 @@
@Test
public void testImeTargetChangeListener_OnImeInputTargetVisibilityChanged() {
- final TestImeTargetChangeListener listener = new TestImeTargetChangeListener();
- mWm.mImeTargetChangeListener = listener;
+ final InputMethodManagerInternal immi = InputMethodManagerInternal.get();
+ spyOn(immi);
final WindowState imeTarget = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
createActivityRecord(mDisplayContent), "imeTarget");
@@ -1347,32 +1350,26 @@
makeWindowVisible(imeTarget);
mDisplayContent.setImeInputTarget(imeTarget);
waitHandlerIdle(mWm.mH);
-
- assertThat(listener.mImeTargetToken).isEqualTo(imeTarget.mClient.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeInputTarget).isTrue();
- assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
+ verify(immi).onImeInputTargetVisibilityChanged(imeTarget.mClient.asBinder(),
+ true /* visibleAndNotRemoved */, mDisplayContent.getDisplayId());
+ reset(immi);
imeTarget.mActivityRecord.setVisibleRequested(false);
waitHandlerIdle(mWm.mH);
-
- assertThat(listener.mImeTargetToken).isEqualTo(imeTarget.mClient.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeInputTarget).isFalse();
- assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
+ verify(immi).onImeInputTargetVisibilityChanged(imeTarget.mClient.asBinder(),
+ false /* visibleAndNotRemoved */, mDisplayContent.getDisplayId());
+ reset(immi);
imeTarget.removeImmediately();
- assertThat(listener.mImeTargetToken).isEqualTo(imeTarget.mClient.asBinder());
- assertThat(listener.mIsRemoved).isTrue();
- assertThat(listener.mIsVisibleForImeInputTarget).isFalse();
- assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
+ verify(immi).onImeInputTargetVisibilityChanged(imeTarget.mClient.asBinder(),
+ false /* visibleAndNotRemoved */, mDisplayContent.getDisplayId());
}
@SetupWindows(addWindows = {W_INPUT_METHOD})
@Test
public void testImeTargetChangeListener_OnImeTargetOverlayVisibilityChanged() {
- final TestImeTargetChangeListener listener = new TestImeTargetChangeListener();
- mWm.mImeTargetChangeListener = listener;
+ final InputMethodManagerInternal immi = InputMethodManagerInternal.get();
+ spyOn(immi);
// Scenario 1: test addWindow/relayoutWindow to add Ime layering overlay window as visible.
final WindowToken windowToken = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
@@ -1402,10 +1399,10 @@
final WindowState imeLayeringTargetOverlay = mDisplayContent.getWindow(
w -> w.mClient.asBinder() == client.asBinder());
assertThat(imeLayeringTargetOverlay.isVisible()).isTrue();
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isTrue();
- assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
+ verify(immi, atLeast(1))
+ .setHasVisibleImeLayeringOverlay(true /* hasVisibleOverlay */,
+ mDisplayContent.getDisplayId());
+ reset(immi);
// Scenario 2: test relayoutWindow to let the Ime layering target overlay window invisible.
mWm.relayoutWindow(session, client, params, 100, 200, View.GONE, 0, 0, 0,
@@ -1413,19 +1410,16 @@
waitHandlerIdle(mWm.mH);
assertThat(imeLayeringTargetOverlay.isVisible()).isFalse();
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
- assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
+ verify(immi).setHasVisibleImeLayeringOverlay(false /* hasVisibleOverlay */,
+ mDisplayContent.getDisplayId());
+ reset(immi);
// Scenario 3: test removeWindow to remove the Ime layering target overlay window.
mWm.removeClientToken(session, client.asBinder());
waitHandlerIdle(mWm.mH);
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isTrue();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
- assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
+ verify(immi).setHasVisibleImeLayeringOverlay(false /* hasVisibleOverlay */,
+ mDisplayContent.getDisplayId());
}
@Test
@@ -1468,31 +1462,4 @@
mWm.mSensitiveContentPackages.removeBlockScreenCaptureForApps(blockedPackages);
assertFalse(window.isSecureLocked());
}
-
- private static class TestImeTargetChangeListener implements ImeTargetChangeListener {
- private IBinder mImeTargetToken;
- private boolean mIsRemoved;
- private boolean mIsVisibleForImeTargetOverlay;
- private boolean mIsVisibleForImeInputTarget;
- private int mDisplayId;
-
- @Override
- public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed, int displayId) {
- mImeTargetToken = overlayWindowToken;
- mIsVisibleForImeTargetOverlay = visible;
- mIsRemoved = removed;
- mDisplayId = displayId;
- }
-
- @Override
- public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed, int displayId) {
- mImeTargetToken = imeInputTarget;
- mIsVisibleForImeInputTarget = visibleRequested;
- mIsRemoved = removed;
- mDisplayId = displayId;
- }
- }
}
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 5524541..a01a720 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -267,6 +267,17 @@
"android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED";
/**
+ * Bundle key for the {@code resolvedBundle} passed to {@link #onDownloadSubscription(
+ * int, int, DownloadableSubscription, boolean, boolean, Bundle)}. The value is a
+ * {@link String} for the package name of the app calling the
+ * {@link EuiccManager#downloadSubscription(int, DownloadableSubscription, PendingIntent)} API.
+ * This is to be used by LPA to determine the app that is requesting the download.
+ *
+ * @hide
+ */
+ public static final String EXTRA_PACKAGE_NAME = "android.service.euicc.extra.PACKAGE_NAME";
+
+ /**
* Intent extra set for resolution requests containing an int indicating the current card Id.
*/
public static final String EXTRA_RESOLUTION_CARD_ID =
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index dea10b70..f0850af 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1177,6 +1177,16 @@
*/
public static final String SATELLITE_ESOS_SUPPORTED = SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED;
+ /**
+ * TelephonyProvider column name for satellite provisioned status. The value of this
+ * column is set based on whether carrier roaming NB-IOT satellite service is provisioned or
+ * not. By default, it's disabled.
+ *
+ * @hide
+ */
+ public static final String IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM =
+ SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 0bd9270..ad6db2d 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -412,6 +412,14 @@
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26;
+ /**
+ * Emergency call is in progress.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27;
+
/** @hide */
@IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
SATELLITE_RESULT_SUCCESS,
@@ -440,7 +448,8 @@
SATELLITE_RESULT_ILLEGAL_STATE,
SATELLITE_RESULT_MODEM_TIMEOUT,
SATELLITE_RESULT_LOCATION_DISABLED,
- SATELLITE_RESULT_LOCATION_NOT_AVAILABLE
+ SATELLITE_RESULT_LOCATION_NOT_AVAILABLE,
+ SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteResult {}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index b82396e..e66a082 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -206,77 +206,6 @@
void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback);
/**
- * Provision the device with a satellite provider.
- * This is needed if the provider allows dynamic registration.
- * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
- *
- * @param token The token to be used as a unique identifier for provisioning with satellite
- * gateway.
- * @param provisionData Data from the provisioning app that can be used by provisioning server
- * @param resultCallback The callback to receive the error code result of the operation.
- *
- * Valid result codes returned:
- * SatelliteResult:SATELLITE_RESULT_SUCCESS
- * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
- * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
- * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
- * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
- * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
- * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
- * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
- * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
- * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
- * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
- */
- void provisionSatelliteService(in String token, in byte[] provisionData,
- in IIntegerConsumer resultCallback);
-
- /**
- * Deprovision the device with the satellite provider.
- * This is needed if the provider allows dynamic registration.
- * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
- *
- * @param token The token of the device/subscription to be deprovisioned.
- * @param resultCallback The callback to receive the error code result of the operation.
- *
- * Valid result codes returned:
- * SatelliteResult:SATELLITE_RESULT_SUCCESS
- * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
- * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
- * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
- * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
- * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
- * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
- * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
- * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
- * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
- * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
- */
- void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback);
-
- /**
- * Request to get whether this device is provisioned with a satellite provider.
- *
- * @param resultCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not
- * SatelliteResult#SATELLITE_RESULT_SUCCESS.
- * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
- * receive whether this device is provisioned with a satellite provider.
- *
- * Valid result codes returned:
- * SatelliteResult:SATELLITE_RESULT_SUCCESS
- * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
- * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
- * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
- * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
- * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
- * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
- * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
- */
- void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback,
- in IBooleanConsumer callback);
-
- /**
* Poll the pending datagrams to be received over satellite.
* The satellite service should check if there are any pending datagrams to be received over
* satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index b4eb15fd..3f2fce2 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -28,13 +28,6 @@
*/
oneway interface ISatelliteListener {
/**
- * Indicates that the satellite provision state has changed.
- *
- * @param provisioned True means the service is provisioned and false means it is not.
- */
- void onSatelliteProvisionStateChanged(in boolean provisioned);
-
- /**
* Indicates that new datagrams have been received on the device.
*
* @param datagram New datagram that was received.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index d8b4974..c50e469 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -142,32 +142,6 @@
}
@Override
- public void provisionSatelliteService(String token, byte[] provisionData,
- IIntegerConsumer resultCallback) throws RemoteException {
- executeMethodAsync(
- () -> SatelliteImplBase.this
- .provisionSatelliteService(token, provisionData, resultCallback),
- "provisionSatelliteService");
- }
-
- @Override
- public void deprovisionSatelliteService(String token, IIntegerConsumer resultCallback)
- throws RemoteException {
- executeMethodAsync(
- () -> SatelliteImplBase.this.deprovisionSatelliteService(token, resultCallback),
- "deprovisionSatelliteService");
- }
-
- @Override
- public void requestIsSatelliteProvisioned(IIntegerConsumer resultCallback,
- IBooleanConsumer callback) throws RemoteException {
- executeMethodAsync(
- () -> SatelliteImplBase.this
- .requestIsSatelliteProvisioned(resultCallback, callback),
- "requestIsSatelliteProvisioned");
- }
-
- @Override
public void pollPendingSatelliteDatagrams(IIntegerConsumer resultCallback)
throws RemoteException {
executeMethodAsync(
@@ -487,85 +461,6 @@
}
/**
- * Provision the device with a satellite provider.
- * This is needed if the provider allows dynamic registration.
- * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
- *
- * @param token The token to be used as a unique identifier for provisioning with satellite
- * gateway.
- * @param provisionData Data from the provisioning app that can be used by provisioning
- * server
- * @param resultCallback The callback to receive the error code result of the operation.
- *
- * Valid result codes returned:
- * SatelliteResult:SATELLITE_RESULT_SUCCESS
- * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
- * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
- * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
- * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
- * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
- * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
- * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
- * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
- * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
- * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
- */
- public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
- @NonNull IIntegerConsumer resultCallback) {
- // stub implementation
- }
-
- /**
- * Deprovision the device with the satellite provider.
- * This is needed if the provider allows dynamic registration.
- * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
- *
- * @param token The token of the device/subscription to be deprovisioned.
- * @param resultCallback The callback to receive the error code result of the operation.
- *
- * Valid result codes returned:
- * SatelliteResult:SATELLITE_RESULT_SUCCESS
- * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
- * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
- * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR
- * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
- * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
- * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
- * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
- * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
- * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED
- * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT
- */
- public void deprovisionSatelliteService(@NonNull String token,
- @NonNull IIntegerConsumer resultCallback) {
- // stub implementation
- }
-
- /**
- * Request to get whether this device is provisioned with a satellite provider.
- *
- * @param resultCallback The callback to receive the error code result of the operation.
- * This must only be sent when the result is not
- * SatelliteResult#SATELLITE_RESULT_SUCCESS.
- * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to
- * receive whether this device is provisioned with a satellite provider.
- *
- * Valid result codes returned:
- * SatelliteResult:SATELLITE_RESULT_SUCCESS
- * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
- * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
- * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
- * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS
- * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
- * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
- * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
- */
- public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer resultCallback,
- @NonNull IBooleanConsumer callback) {
- // stub implementation
- }
-
- /**
* Poll the pending datagrams to be received over satellite.
* The satellite service should check if there are any pending datagrams to be received over
* satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3dbda7a..2f8e957 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3007,16 +3007,18 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void setDeviceAlignedWithSatellite(int subId, in boolean isAligned);
+ void setDeviceAlignedWithSatellite(int subId, boolean isAligned);
/**
* This API can be used by only CTS to update satellite vendor service package name.
*
* @param servicePackageName The package name of the satellite vendor service.
+ * @param provisioned Whether satellite should be provisioned or not.
+ *
* @return {@code true} if the satellite vendor service is set successfully,
* {@code false} otherwise.
*/
- boolean setSatelliteServicePackageName(in String servicePackageName);
+ boolean setSatelliteServicePackageName(in String servicePackageName, in String provisioned);
/**
* This API can be used by only CTS to update satellite gateway service package name.
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 5976657..71f3033 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -47,6 +47,10 @@
compile_dex: true,
default_to_stubs: true,
dist_group: "android",
+
+ // This module cannot generate stubs from the api signature files as stubs depends on the
+ // private APIs, which are not visible in the api signature files.
+ build_from_text_stub: false,
}
java_library {
diff --git a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
new file mode 100644
index 0000000..24d7291
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyboardSystemShortcutListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardSystemShortcutListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardSystemShortcutListenerTest {
+
+ companion object {
+ const val DEVICE_ID = 1
+ val HOME_SHORTCUT = KeyboardSystemShortcut(
+ intArrayOf(KeyEvent.KEYCODE_H),
+ KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
+ )
+ }
+
+ @get:Rule
+ val rule = SetFlagsRule()
+
+ private val testLooper = TestLooper()
+ private val executor = HandlerExecutor(Handler(testLooper.looper))
+ private var registeredListener: IKeyboardSystemShortcutListener? = null
+ private lateinit var context: Context
+ private lateinit var inputManager: InputManager
+ private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+ @Mock
+ private lateinit var iInputManagerMock: IInputManager
+
+ @Before
+ fun setUp() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+ inputManager = InputManager(context)
+ `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+
+ // Handle keyboard system shortcut listener registration.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
+ if (registeredListener != null &&
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ // There can only be one registered keyboard system shortcut listener per process.
+ fail("Trying to register a new listener when one already exists")
+ }
+ registeredListener = listener
+ null
+ }.`when`(iInputManagerMock).registerKeyboardSystemShortcutListener(any())
+
+ // Handle keyboard system shortcut listener being unregistered.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
+ if (registeredListener == null ||
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ fail("Trying to unregister a listener that is not registered")
+ }
+ registeredListener = null
+ null
+ }.`when`(iInputManagerMock).unregisterKeyboardSystemShortcutListener(any())
+ }
+
+ @After
+ fun tearDown() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ }
+
+ private fun notifyKeyboardSystemShortcutTriggered(id: Int, shortcut: KeyboardSystemShortcut) {
+ registeredListener!!.onKeyboardSystemShortcutTriggered(
+ id,
+ shortcut.keycodes,
+ shortcut.modifierState,
+ shortcut.systemShortcut
+ )
+ }
+
+ @Test
+ fun testListenerHasCorrectSystemShortcutNotified() {
+ var callbackCount = 0
+
+ // Add a keyboard system shortcut listener
+ inputManager.registerKeyboardSystemShortcutListener(executor) {
+ deviceId: Int, systemShortcut: KeyboardSystemShortcut ->
+ assertEquals(DEVICE_ID, deviceId)
+ assertEquals(HOME_SHORTCUT, systemShortcut)
+ callbackCount++
+ }
+
+ // Notifying keyboard system shortcut triggered will notify the listener.
+ notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount)
+ }
+
+ @Test
+ fun testAddingListenersRegistersInternalCallbackListener() {
+ // Set up two callbacks.
+ val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+ val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+
+ assertNull(registeredListener)
+
+ // Adding the listener should register the callback with InputManagerService.
+ inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+ assertNotNull(registeredListener)
+
+ // Adding another listener should not register new internal listener.
+ val currListener = registeredListener
+ inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+ assertEquals(currListener, registeredListener)
+ }
+
+ @Test
+ fun testRemovingListenersUnregistersInternalCallbackListener() {
+ // Set up two callbacks.
+ val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+ val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
+
+ inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+ inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+
+ // Only removing all listeners should remove the internal callback
+ inputManager.unregisterKeyboardSystemShortcutListener(callback1)
+ assertNotNull(registeredListener)
+ inputManager.unregisterKeyboardSystemShortcutListener(callback2)
+ assertNull(registeredListener)
+ }
+
+ @Test
+ fun testMultipleListeners() {
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount1++ }
+ val callback2 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount2++ }
+
+ // Add both keyboard system shortcut listeners
+ inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
+ inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
+
+ // Notifying keyboard system shortcut triggered, should notify both the callbacks.
+ notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+ testLooper.dispatchAll()
+ assertEquals(1, callbackCount1)
+ assertEquals(1, callbackCount2)
+
+ inputManager.unregisterKeyboardSystemShortcutListener(callback2)
+ // Notifying keyboard system shortcut triggered, should still trigger callback1 but not
+ // callback2.
+ notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+ testLooper.dispatchAll()
+ assertEquals(2, callbackCount1)
+ assertEquals(1, callbackCount2)
+ }
+}
diff --git a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
new file mode 100644
index 0000000..5a40a1c
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IKeyboardSystemShortcutListener
+import android.hardware.input.KeyboardSystemShortcut
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link KeyboardShortcutCallbackHandler}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardShortcutCallbackHandlerTests
+ */
+@Presubmit
+class KeyboardShortcutCallbackHandlerTests {
+
+ companion object {
+ val DEVICE_ID = 1
+ val HOME_SHORTCUT = KeyboardSystemShortcut(
+ intArrayOf(KeyEvent.KEYCODE_H),
+ KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
+ )
+ }
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ private lateinit var keyboardShortcutCallbackHandler: KeyboardShortcutCallbackHandler
+ private lateinit var context: Context
+ private var lastShortcut: KeyboardSystemShortcut? = null
+
+ @Before
+ fun setup() {
+ context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ keyboardShortcutCallbackHandler = KeyboardShortcutCallbackHandler()
+ }
+
+ @Test
+ fun testKeyboardSystemShortcutTriggered_registerUnregisterListener() {
+ val listener = KeyboardSystemShortcutListener()
+
+ // Register keyboard system shortcut listener
+ keyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, 0)
+ keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+ assertEquals(
+ "Listener should get callback on keyboard system shortcut triggered",
+ HOME_SHORTCUT,
+ lastShortcut!!
+ )
+
+ // Unregister listener
+ lastShortcut = null
+ keyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, 0)
+ keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
+ assertNull("Listener should not get callback after being unregistered", lastShortcut)
+ }
+
+ inner class KeyboardSystemShortcutListener : IKeyboardSystemShortcutListener.Stub() {
+ override fun onKeyboardSystemShortcutTriggered(
+ deviceId: Int,
+ keycodes: IntArray,
+ modifierState: Int,
+ shortcut: Int
+ ) {
+ assertEquals(DEVICE_ID, deviceId)
+ lastShortcut = KeyboardSystemShortcut(keycodes, modifierState, shortcut)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index 827ff4f..ad98e47 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -24,6 +24,7 @@
"flickerlib-parsers",
"perfetto_trace_java_protos",
"flickerlib-trace_processor_shell",
+ "ravenwood-junit",
],
java_resource_dirs: ["res"],
certificate: "platform",
@@ -39,6 +40,7 @@
"platform-test-annotations",
],
srcs: [
+ "src/com/android/internal/graphics/ColorUtilsTest.java",
"src/com/android/internal/util/ParcellingTests.java",
],
auto_gen_config: true,
diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
index d0bb8e3..38a22f2 100644
--- a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
+++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
@@ -19,14 +19,19 @@
import static org.junit.Assert.assertTrue;
import android.graphics.Color;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
@SmallTest
public class ColorUtilsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
@Test
public void calculateMinimumBackgroundAlpha_satisfiestContrast() {
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
new file mode 100644
index 0000000..e3ec62d
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.endsWith;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogCommandHandlerTest {
+
+ @Mock
+ ProtoLogService mProtoLogService;
+ @Mock
+ PrintWriter mPrintWriter;
+
+ @Test
+ public void printsHelpForAllAvailableCommands() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.onHelp();
+ validateOnHelpPrinted();
+ }
+
+ @Test
+ public void printsHelpIfCommandIsNull() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.onCommand(null);
+ validateOnHelpPrinted();
+ }
+
+ @Test
+ public void handlesGroupListCommand() {
+ Mockito.when(mProtoLogService.getGroups())
+ .thenReturn(new String[] {"MY_TEST_GROUP", "MY_OTHER_GROUP"});
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "groups", "list" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_TEST_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_OTHER_GROUP"));
+ }
+
+ @Test
+ public void handlesIncompleteGroupsCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "groups" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommand() {
+ Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {"MY_GROUP"});
+ Mockito.when(mProtoLogService.isLoggingToLogcat("MY_GROUP")).thenReturn(true);
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "groups", "status", "MY_GROUP" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("LOG_TO_LOGCAT = true"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommandOfUnregisteredGroups() {
+ Mockito.when(mProtoLogService.getGroups()).thenReturn(new String[] {});
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "groups", "status", "MY_GROUP" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("MY_GROUP"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("UNREGISTERED"));
+ }
+
+ @Test
+ public void handlesGroupStatusCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "groups", "status" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesIncompleteLogcatCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat" });
+
+ Mockito.verify(mPrintWriter, times(1))
+ .println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesLogcatEnableCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat", "enable", "MY_GROUP" });
+ Mockito.verify(mProtoLogService).enableProtoLogToLogcat("MY_GROUP");
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" });
+ Mockito.verify(mProtoLogService)
+ .enableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ }
+
+ @Test
+ public void handlesLogcatDisableCommand() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat", "disable", "MY_GROUP" });
+ Mockito.verify(mProtoLogService).disableProtoLogToLogcat("MY_GROUP");
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" });
+ Mockito.verify(mProtoLogService)
+ .disableProtoLogToLogcat("MY_GROUP", "MY_OTHER_GROUP");
+ }
+
+ @Test
+ public void handlesLogcatEnableCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat", "enable" });
+ Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+ }
+
+ @Test
+ public void handlesLogcatDisableCommandWithNoGroups() {
+ final ProtoLogCommandHandler cmdHandler =
+ new ProtoLogCommandHandler(mProtoLogService, mPrintWriter);
+
+ cmdHandler.exec(mProtoLogService, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
+ new String[] { "logcat", "disable" });
+ Mockito.verify(mPrintWriter).println(contains("Incomplete command"));
+ }
+
+ private void validateOnHelpPrinted() {
+ Mockito.verify(mPrintWriter, times(1)).println(endsWith("help"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(endsWith("groups (list | status)"));
+ Mockito.verify(mPrintWriter, times(1))
+ .println(endsWith("logcat (enable | disable) <group>"));
+ Mockito.verify(mPrintWriter, atLeast(0)).println(anyString());
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
new file mode 100644
index 0000000..feac59c
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogServiceTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.tools.ScenarioBuilder;
+import android.tools.Tag;
+import android.tools.io.ResultArtifactDescriptor;
+import android.tools.io.TraceType;
+import android.tools.traces.TraceConfig;
+import android.tools.traces.TraceConfigs;
+import android.tools.traces.io.ResultReader;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+
+import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import perfetto.protos.Protolog.ProtoLogViewerConfig;
+import perfetto.protos.ProtologCommon;
+import perfetto.protos.TraceOuterClass.Trace;
+import perfetto.protos.TracePacketOuterClass.TracePacket;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class ProtoLogServiceTest {
+
+ private static final String TEST_GROUP = "MY_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP = "MY_OTHER_TEST_GROUP";
+
+ private static final ProtoLogViewerConfig VIEWER_CONFIG =
+ ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP)
+ .setTag(TEST_GROUP)
+ ).addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Debug Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Verbose Log Message %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).build();
+
+ @Mock
+ IProtoLogClient mMockClient;
+
+ @Mock
+ IProtoLogClient mSecondMockClient;
+
+ @Mock
+ IBinder mMockClientBinder;
+
+ @Mock
+ IBinder mSecondMockClientBinder;
+
+ private final File mTracingDirectory = createTempDirectory("temp").toFile();
+
+ private final ResultWriter mWriter = new ResultWriter()
+ .forScenario(new ScenarioBuilder()
+ .forClass(createTempFile("temp", "").getName()).build())
+ .withOutputDir(mTracingDirectory)
+ .setRunComplete();
+
+ private final TraceConfigs mTraceConfig = new TraceConfigs(
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false),
+ new TraceConfig(false, true, false)
+ );
+
+ @Captor
+ ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientArgumentCaptor;
+
+ @Captor
+ ArgumentCaptor<IBinder.DeathRecipient> mSecondDeathRecipientArgumentCaptor;
+
+ private File mViewerConfigFile;
+
+ public ProtoLogServiceTest() throws IOException {
+ }
+
+ @Before
+ public void setUp() {
+ Mockito.when(mMockClient.asBinder()).thenReturn(mMockClientBinder);
+ Mockito.when(mSecondMockClient.asBinder()).thenReturn(mSecondMockClientBinder);
+
+ try {
+ mViewerConfigFile = File.createTempFile("viewer-config", ".pb");
+ try (var fos = new FileOutputStream(mViewerConfigFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+
+ bos.write(VIEWER_CONFIG.toByteArray());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void canRegisterClientWithGroupsOnly() throws RemoteException {
+ final ProtoLogService service = new ProtoLogService();
+
+ final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+ Truth.assertThat(service.getGroups()).asList().containsExactly(TEST_GROUP);
+ }
+
+ @Test
+ public void willDumpViewerConfigOnlyOnceOnTraceStop()
+ throws RemoteException, InvalidProtocolBufferException {
+ final ProtoLogService service = new ProtoLogService();
+
+ final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ service.registerClient(mMockClient, args);
+ service.registerClient(mSecondMockClient, args);
+
+ PerfettoTraceMonitor traceMonitor =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
+
+ traceMonitor.start();
+ traceMonitor.stop(mWriter);
+ final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
+ final byte[] traceData = reader.getArtifact()
+ .readBytes(new ResultArtifactDescriptor(TraceType.PERFETTO, Tag.ALL));
+
+ final Trace trace = Trace.parseFrom(traceData);
+
+ final List<TracePacket> configPackets = trace.getPacketList().stream()
+ .filter(it -> it.hasProtologViewerConfig())
+ // Exclude viewer configs from regular system tracing
+ .filter(it ->
+ it.getProtologViewerConfig().getGroups(0).getName().equals(TEST_GROUP))
+ .toList();
+ Truth.assertThat(configPackets).hasSize(1);
+ Truth.assertThat(configPackets.get(0).getProtologViewerConfig().toString())
+ .isEqualTo(VIEWER_CONFIG.toString());
+ }
+
+ @Test
+ public void willDumpViewerConfigOnLastClientDisconnected()
+ throws RemoteException, FileNotFoundException {
+ final ProtoLogService.ViewerConfigFileTracer tracer =
+ Mockito.mock(ProtoLogService.ViewerConfigFileTracer.class);
+ final ProtoLogService service = new ProtoLogService(tracer);
+
+ final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(
+ TEST_GROUP, true))
+ .setViewerConfigFile(mViewerConfigFile.getAbsolutePath());
+ service.registerClient(mMockClient, args);
+ service.registerClient(mSecondMockClient, args);
+
+ Mockito.verify(mMockClientBinder)
+ .linkToDeath(mDeathRecipientArgumentCaptor.capture(), anyInt());
+ Mockito.verify(mSecondMockClientBinder)
+ .linkToDeath(mSecondDeathRecipientArgumentCaptor.capture(), anyInt());
+
+ mDeathRecipientArgumentCaptor.getValue().binderDied();
+ Mockito.verify(tracer, never()).trace(any(), any());
+ mSecondDeathRecipientArgumentCaptor.getValue().binderDied();
+ Mockito.verify(tracer).trace(any(), eq(mViewerConfigFile.getAbsolutePath()));
+ }
+
+ @Test
+ public void sendEnableLoggingToLogcatToClient() throws RemoteException {
+ final var service = new ProtoLogService();
+
+ final var args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+ service.enableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(true),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+
+ @Test
+ public void sendDisableLoggingToLogcatToClient() throws RemoteException {
+ final ProtoLogService service = new ProtoLogService();
+
+ final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, true));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+ service.disableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(false),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+
+ @Test
+ public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException {
+ final ProtoLogService service = new ProtoLogService();
+
+ final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+ service.enableProtoLogToLogcat(OTHER_TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isFalse();
+
+ Mockito.verify(mMockClient, never()).toggleLogcat(anyBoolean(), any());
+ }
+
+ @Test
+ public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException {
+ final ProtoLogService service = new ProtoLogService();
+
+ Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP);
+ service.enableProtoLogToLogcat(TEST_GROUP);
+ Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue();
+
+ final ProtoLogService.RegisterClientArgs args = new ProtoLogService.RegisterClientArgs()
+ .setGroups(new ProtoLogService.RegisterClientArgs.GroupConfig(TEST_GROUP, false));
+ service.registerClient(mMockClient, args);
+
+ Mockito.verify(mMockClient).toggleLogcat(eq(true),
+ Mockito.argThat(it -> it.length == 1 && it[0].equals(TEST_GROUP)));
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
index 65a3436..fb63422 100644
--- a/tests/Internal/src/com/android/internal/util/ParcellingTests.java
+++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java
@@ -18,6 +18,7 @@
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -26,6 +27,7 @@
import com.android.internal.util.Parcelling.BuiltIn.ForInstant;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -38,6 +40,9 @@
@RunWith(JUnit4.class)
public class ParcellingTests {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private Parcel mParcel = Parcel.obtain();
@Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 3f9016b..f43cf52 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -113,6 +113,7 @@
"io/ZipArchive.cpp",
"link/AutoVersioner.cpp",
"link/FeatureFlagsFilter.cpp",
+ "link/FlagDisabledResourceRemover.cpp",
"link/ManifestFixer.cpp",
"link/NoDefaultResourceRemover.cpp",
"link/PrivateAttributeMover.cpp",
@@ -189,6 +190,8 @@
"integration-tests/CommandTests/**/*",
"integration-tests/ConvertTest/**/*",
"integration-tests/DumpTest/**/*",
+ ":resource-flagging-test-app-apk",
+ ":resource-flagging-test-app-r-java",
],
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9444dd9..1c85e9f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -690,9 +690,7 @@
resource_format = item_iter->second.format;
}
- // Don't bother parsing the item if it is behind a disabled flag
- if (out_resource->flag_status != FlagStatus::Disabled &&
- !ParseItem(parser, out_resource, resource_format)) {
+ if (!ParseItem(parser, out_resource, resource_format)) {
return false;
}
return true;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 2e6ad13..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -69,13 +69,8 @@
return TestParse(str, ConfigDescription{});
}
- ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) {
- return TestParse(str, ConfigDescription{}, parserOptions);
- }
-
- ::testing::AssertionResult TestParse(
- StringPiece str, const ConfigDescription& config,
- ResourceParserOptions parserOptions = ResourceParserOptions()) {
+ ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
+ ResourceParserOptions parserOptions;
ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
parserOptions);
@@ -247,19 +242,6 @@
EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)"));
}
-TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) {
- FeatureFlagProperties flag_properties(true, false);
- ResourceParserOptions options;
- options.feature_flag_values = {{"falseFlag", flag_properties}};
- ASSERT_TRUE(TestParse(
- R"(<string name="foo" android:featureFlag="falseFlag"
- xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)",
- options));
-
- String* str = test::GetValue<String>(&table_, "string/foo");
- ASSERT_THAT(str, IsNull());
-}
-
TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) {
std::string input = R"(
<string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 642a561..56f5288 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -57,6 +57,7 @@
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/FeatureFlagsFilter.h"
+#include "link/FlagDisabledResourceRemover.h"
#include "link/Linkers.h"
#include "link/ManifestFixer.h"
#include "link/NoDefaultResourceRemover.h"
@@ -1840,11 +1841,57 @@
return validate(attr->value);
}
+ class FlagDisabledStringVisitor : public DescendingValueVisitor {
+ public:
+ using DescendingValueVisitor::Visit;
+
+ explicit FlagDisabledStringVisitor(android::StringPool& string_pool)
+ : string_pool_(string_pool) {
+ }
+
+ void Visit(RawString* value) override {
+ value->value = string_pool_.MakeRef("");
+ }
+
+ void Visit(String* value) override {
+ value->value = string_pool_.MakeRef("");
+ }
+
+ void Visit(StyledString* value) override {
+ value->value = string_pool_.MakeRef(android::StyleString{{""}, {}});
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FlagDisabledStringVisitor);
+ android::StringPool& string_pool_;
+ };
+
// Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
// to the IArchiveWriter.
bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
ResourceTable* table) {
TRACE_CALL();
+
+ FlagDisabledStringVisitor visitor(table->string_pool);
+
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ for (auto& config_value : entry->values) {
+ if (config_value->flag_status == FlagStatus::Disabled) {
+ config_value->value->Accept(&visitor);
+ }
+ }
+ }
+ }
+ }
+
+ if (!FlagDisabledResourceRemover{}.Consume(context_, table)) {
+ context_->GetDiagnostics()->Error(android::DiagMessage()
+ << "failed removing resources behind disabled flags");
+ return 1;
+ }
+
const bool keep_raw_values = (context_->GetPackageType() == PackageType::kStaticLib)
|| options_.keep_raw_values;
bool result = FlattenXml(context_, *manifest, kAndroidManifestPath, keep_raw_values,
@@ -2331,6 +2378,12 @@
return 1;
};
+ if (options_.generate_java_class_path || options_.generate_text_symbols_path) {
+ if (!GenerateJavaClasses()) {
+ return 1;
+ }
+ }
+
if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
return 1;
}
@@ -2339,12 +2392,6 @@
return 1;
}
- if (options_.generate_java_class_path || options_.generate_text_symbols_path) {
- if (!GenerateJavaClasses()) {
- return 1;
- }
- }
-
if (!WriteProguardFile(options_.generate_proguard_rules_path, proguard_keep_set)) {
return 1;
}
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
new file mode 100644
index 0000000..5932271
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES 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"],
+ default_team: "trendy_team_android_resources",
+}
+
+genrule {
+ name: "resource-flagging-test-app-compile",
+ tools: ["aapt2"],
+ srcs: [
+ "res/values/bools.xml",
+ "res/values/bools2.xml",
+ "res/values/strings.xml",
+ ],
+ out: [
+ "values_bools.arsc.flat",
+ "values_bools2.arsc.flat",
+ "values_strings.arsc.flat",
+ ],
+ cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
+ "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+}
+
+genrule {
+ name: "resource-flagging-test-app-apk",
+ tools: ["aapt2"],
+ // The first input file in the list must be the manifest
+ srcs: [
+ "AndroidManifest.xml",
+ ":resource-flagging-test-app-compile",
+ ],
+ out: [
+ "resapp.apk",
+ ],
+ cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
+}
+
+genrule {
+ name: "resource-flagging-test-app-r-java",
+ tools: ["aapt2"],
+ // The first input file in the list must be the manifest
+ srcs: [
+ "AndroidManifest.xml",
+ ":resource-flagging-test-app-compile",
+ ],
+ out: [
+ "resource-flagging-java/com/android/intenal/flaggedresources/R.java",
+ ],
+ cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in)",
+}
+
+java_genrule {
+ name: "resource-flagging-test-app-apk-as-resource",
+ srcs: [
+ ":resource-flagging-test-app-apk",
+ ],
+ out: ["apks_as_resources.res.zip"],
+ tools: ["soong_zip"],
+
+ cmd: "mkdir -p $(genDir)/res/raw && " +
+ "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml
similarity index 100%
rename from core/tests/resourceflaggingtests/TestAppAndroidManifest.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
similarity index 82%
rename from core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 8d01465..3e094fb 100644
--- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -7,4 +7,6 @@
<bool name="res2" android:featureFlag="test.package.trueFlag">true</bool>
<bool name="res3">false</bool>
+
+ <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool>
</resources>
\ No newline at end of file
diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
similarity index 100%
rename from core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml
rename to tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
new file mode 100644
index 0000000..5c0fca1
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <string name="str">plain string</string>
+
+ <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
new file mode 100644
index 0000000..e3289e2
--- /dev/null
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "link/FlagDisabledResourceRemover.h"
+
+#include <algorithm>
+
+#include "ResourceTable.h"
+
+using android::ConfigDescription;
+
+namespace aapt {
+
+static bool KeepResourceEntry(const std::unique_ptr<ResourceEntry>& entry) {
+ if (entry->values.empty()) {
+ return true;
+ }
+ const auto end_iter = entry->values.end();
+ const auto remove_iter =
+ std::stable_partition(entry->values.begin(), end_iter,
+ [](const std::unique_ptr<ResourceConfigValue>& value) -> bool {
+ return value->flag_status != FlagStatus::Disabled;
+ });
+
+ bool keep = remove_iter != entry->values.begin();
+
+ entry->values.erase(remove_iter, end_iter);
+ return keep;
+}
+
+bool FlagDisabledResourceRemover::Consume(IAaptContext* context, ResourceTable* table) {
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ const auto end_iter = type->entries.end();
+ const auto remove_iter = std::stable_partition(
+ type->entries.begin(), end_iter, [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
+ return KeepResourceEntry(entry);
+ });
+
+ type->entries.erase(remove_iter, end_iter);
+ }
+ }
+ return true;
+}
+
+} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.h b/tools/aapt2/link/FlagDisabledResourceRemover.h
new file mode 100644
index 0000000..2db2cb4
--- /dev/null
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "android-base/macros.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+// Removes any resource that are behind disabled flags.
+class FlagDisabledResourceRemover : public IResourceTableConsumer {
+ public:
+ FlagDisabledResourceRemover() = default;
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FlagDisabledResourceRemover);
+};
+
+} // namespace aapt
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
new file mode 100644
index 0000000..c901b58
--- /dev/null
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LoadedApk.h"
+#include "cmd/Dump.h"
+#include "io/StringStream.h"
+#include "test/Test.h"
+#include "text/Printer.h"
+
+using ::aapt::io::StringOutputStream;
+using ::aapt::text::Printer;
+using testing::Eq;
+using testing::Ne;
+
+namespace aapt {
+
+using FlaggedResourcesTest = CommandTestFixture;
+
+static android::NoOpDiagnostics noop_diag;
+
+void DumpStringPoolToString(LoadedApk* loaded_apk, std::string* output) {
+ StringOutputStream output_stream(output);
+ Printer printer(&output_stream);
+
+ DumpStringsCommand command(&printer, &noop_diag);
+ ASSERT_EQ(command.Dump(loaded_apk), 0);
+ output_stream.Flush();
+}
+
+void DumpResourceTableToString(LoadedApk* loaded_apk, std::string* output) {
+ StringOutputStream output_stream(output);
+ Printer printer(&output_stream);
+
+ DumpTableCommand command(&printer, &noop_diag);
+ ASSERT_EQ(command.Dump(loaded_apk), 0);
+ output_stream.Flush();
+}
+
+void DumpChunksToString(LoadedApk* loaded_apk, std::string* output) {
+ StringOutputStream output_stream(output);
+ Printer printer(&output_stream);
+
+ DumpChunks command(&printer, &noop_diag);
+ ASSERT_EQ(command.Dump(loaded_apk), 0);
+ output_stream.Flush();
+}
+
+TEST_F(FlaggedResourcesTest, DisabledStringRemovedFromPool) {
+ auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+ auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+ std::string output;
+ DumpStringPoolToString(loaded_apk.get(), &output);
+
+ std::string excluded = "DONTFIND";
+ ASSERT_EQ(output.find(excluded), std::string::npos);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) {
+ auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+ auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+ std::string output;
+ DumpResourceTableToString(loaded_apk.get(), &output);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
+ auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
+ auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
+
+ std::string output;
+ DumpChunksToString(loaded_apk.get(), &output);
+
+ ASSERT_EQ(output.find("res4"), std::string::npos);
+ ASSERT_EQ(output.find("str1"), std::string::npos);
+}
+
+TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
+ auto r_path = file::BuildPath({android::base::GetExecutableDirectory(), "resource-flagging-java",
+ "com", "android", "intenal", "flaggedresources", "R.java"});
+ std::string r_contents;
+ ::android::base::ReadFileToString(r_path, &r_contents);
+
+ ASSERT_NE(r_contents.find("public static final int res4"), std::string::npos);
+ ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
+}
+
+} // namespace aapt
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 73cfd3f..36bfbef 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -58,17 +58,19 @@
// Dump the classes, if specified.
options.inputJarDumpFile.ifSet {
- PrintWriter(it).use { pw -> allClasses.dump(pw) }
- log.i("Dump file created at $it")
+ log.iTime("Dump file created at $it") {
+ PrintWriter(it).use { pw -> allClasses.dump(pw) }
+ }
}
options.inputJarAsKeepAllFile.ifSet {
- PrintWriter(it).use {
- pw -> allClasses.forEach {
- classNode -> printAsTextPolicy(pw, classNode)
+ log.iTime("Dump file created at $it") {
+ PrintWriter(it).use { pw ->
+ allClasses.forEach { classNode ->
+ printAsTextPolicy(pw, classNode)
+ }
}
}
- log.i("Dump file created at $it")
}
// Build the filters.
@@ -85,20 +87,24 @@
errors,
stats,
policyFileRemapper,
+ options.numShards.get,
+ options.shard.get,
)
// Dump statistics, if specified.
options.statsFile.ifSet {
- PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
- log.i("Dump file created at $it")
+ log.iTime("Dump file created at $it") {
+ PrintWriter(it).use { pw -> stats.dumpOverview(pw) }
+ }
}
options.apiListFile.ifSet {
- PrintWriter(it).use { pw ->
- // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
- // framework-minus-apex.jar so that we can dump inherited methods from it.
- ApiDumper(pw, allClasses, null, filter).dump()
+ log.iTime("API list file created at $it") {
+ PrintWriter(it).use { pw ->
+ // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+ // framework-minus-apex.jar so that we can dump inherited methods from it.
+ ApiDumper(pw, allClasses, null, filter).dump()
+ }
}
- log.i("API list file created at $it")
}
}
@@ -213,35 +219,54 @@
errors: HostStubGenErrors,
stats: HostStubGenStats,
remapper: Remapper?,
+ numShards: Int,
+ shard: Int,
) {
log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
- val start = System.currentTimeMillis()
+ log.iTime("Transforming jar") {
+ val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
- val packageRedirector = PackageRedirectRemapper(options.packageRedirects)
+ var itemIndex = 0
+ var numItemsProcessed = 0
+ var numItems = -1 // == Unknown
- log.withIndent {
- // Open the input jar file and process each entry.
- ZipFile(inJar).use { inZip ->
- maybeWithZipOutputStream(outStubJar) { stubOutStream ->
- maybeWithZipOutputStream(outImplJar) { implOutStream ->
- val inEntries = inZip.entries()
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
- convertSingleEntry(inZip, entry, stubOutStream, implOutStream,
+ log.withIndent {
+ // Open the input jar file and process each entry.
+ ZipFile(inJar).use { inZip ->
+
+ numItems = inZip.size()
+ val shardStart = numItems * shard / numShards
+ val shardNextStart = numItems * (shard + 1) / numShards
+
+ maybeWithZipOutputStream(outStubJar) { stubOutStream ->
+ maybeWithZipOutputStream(outImplJar) { implOutStream ->
+ val inEntries = inZip.entries()
+ while (inEntries.hasMoreElements()) {
+ val entry = inEntries.nextElement()
+ val inShard = (shardStart <= itemIndex)
+ && (itemIndex < shardNextStart)
+ itemIndex++
+ if (!inShard) {
+ continue
+ }
+ convertSingleEntry(
+ inZip, entry, stubOutStream, implOutStream,
filter, packageRedirector, remapper,
- enableChecker, classes, errors, stats)
+ enableChecker, classes, errors, stats
+ )
+ numItemsProcessed++
+ }
+ log.i("Converted all entries.")
}
- log.i("Converted all entries.")
}
+ outStubJar?.let { log.i("Created stub: $it") }
+ outImplJar?.let { log.i("Created impl: $it") }
}
- outStubJar?.let { log.i("Created stub: $it") }
- outImplJar?.let { log.i("Created impl: $it") }
}
+ log.i("%d / %d item(s) processed.", numItemsProcessed, numItems)
}
- val end = System.currentTimeMillis()
- log.i("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
}
private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index 18065ba..ee4a06f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -185,6 +185,16 @@
println(LogLevel.Debug, format, *args)
}
+ inline fun <T> iTime(message: String, block: () -> T): T {
+ val start = System.currentTimeMillis()
+ val ret = block()
+ val end = System.currentTimeMillis()
+
+ log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0)
+
+ return ret
+ }
+
inline fun forVerbose(block: () -> Unit) {
if (isEnabled(LogLevel.Verbose)) {
block()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index e192516..2f833a8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -112,6 +112,9 @@
var statsFile: SetOnce<String?> = SetOnce(null),
var apiListFile: SetOnce<String?> = SetOnce(null),
+
+ var numShards: SetOnce<Int> = SetOnce(1),
+ var shard: SetOnce<Int> = SetOnce(0),
) {
companion object {
@@ -162,6 +165,13 @@
fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) }
fun MutableSet<String>.addUniqueAnnotationArg(): String =
nextArg().also { this += ensureUniqueAnnotation(it) }
+ fun SetOnce<Int>.setNextIntArg(): String = nextArg().also {
+ try {
+ this.set(it.toInt())
+ } catch (e: NumberFormatException) {
+ throw ArgumentsException("Invalid integer for $arg: $it")
+ }
+ }
try {
when (arg) {
@@ -259,6 +269,9 @@
"--stats-file" -> ret.statsFile.setNextStringArg()
"--supported-api-list-file" -> ret.apiListFile.setNextStringArg()
+ "--num-shards" -> ret.numShards.setNextIntArg()
+ "--shard-index" -> ret.shard.setNextIntArg()
+
else -> throw ArgumentsException("Unknown option: $arg")
}
} catch (e: SetOnce.SetMoreThanOnceException) {
@@ -396,6 +409,8 @@
enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
statsFile=$statsFile,
apiListFile=$apiListFile,
+ numShards=$numShards,
+ shard=$shard,
}
""".trimIndent()
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 92906a7..2607df6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -184,49 +184,50 @@
* Load all the classes, without code.
*/
fun loadClassStructures(inJar: String): ClassNodes {
- log.i("Reading class structure from $inJar ...")
- val start = System.currentTimeMillis()
+ log.iTime("Reading class structure from $inJar") {
+ val allClasses = ClassNodes()
- val allClasses = ClassNodes()
+ log.withIndent {
+ ZipFile(inJar).use { inZip ->
+ val inEntries = inZip.entries()
- log.withIndent {
- ZipFile(inJar).use { inZip ->
- val inEntries = inZip.entries()
+ while (inEntries.hasMoreElements()) {
+ val entry = inEntries.nextElement()
- while (inEntries.hasMoreElements()) {
- val entry = inEntries.nextElement()
-
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- if (entry.name.endsWith(".class")) {
- val cr = ClassReader(bis)
- val cn = ClassNode()
- cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
- or ClassReader.SKIP_FRAMES)
- if (!allClasses.addClass(cn)) {
- log.w("Duplicate class found: ${cn.name}")
- }
- } else if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw InvalidJarFileException(
- "$inJar is not a desktop jar file. It contains a *.dex file.")
- } else {
- // Unknown file type. Skip.
- while (bis.available() > 0) {
- bis.skip((1024 * 1024).toLong())
+ BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+ if (entry.name.endsWith(".class")) {
+ val cr = ClassReader(bis)
+ val cn = ClassNode()
+ cr.accept(
+ cn, ClassReader.SKIP_CODE
+ or ClassReader.SKIP_DEBUG
+ or ClassReader.SKIP_FRAMES
+ )
+ if (!allClasses.addClass(cn)) {
+ log.w("Duplicate class found: ${cn.name}")
+ }
+ } else if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw InvalidJarFileException(
+ "$inJar is not a desktop jar file."
+ + " It contains a *.dex file."
+ )
+ } else {
+ // Unknown file type. Skip.
+ while (bis.available() > 0) {
+ bis.skip((1024 * 1024).toLong())
+ }
}
}
}
}
}
+ if (allClasses.size == 0) {
+ log.w("$inJar contains no *.class files.")
+ }
+ return allClasses
}
- if (allClasses.size == 0) {
- log.w("$inJar contains no *.class files.")
- }
-
- val end = System.currentTimeMillis()
- log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
- return allClasses
}
}
}
\ No newline at end of file
diff --git a/tools/systemfeatures/OWNERS b/tools/systemfeatures/OWNERS
new file mode 100644
index 0000000..66c8506
--- /dev/null
+++ b/tools/systemfeatures/OWNERS
@@ -0,0 +1 @@
+include /PERFORMANCE_OWNERS
diff --git a/tools/systemfeatures/README.md b/tools/systemfeatures/README.md
new file mode 100644
index 0000000..5836f81
--- /dev/null
+++ b/tools/systemfeatures/README.md
@@ -0,0 +1,11 @@
+# Build-time system feature support
+
+## Overview
+
+System features exposed from `PackageManager` are defined and aggregated as
+`<feature>` xml attributes across various partitions, and are currently queried
+at runtime through the framework. This directory contains tooling that will
+support *build-time* queries of select system features, enabling optimizations
+like code stripping and conditionally dependencies when so configured.
+
+### TODO(b/203143243): Expand readme after landing codegen.
diff --git a/wifi/java/src/android/net/wifi/WifiKeystore.java b/wifi/java/src/android/net/wifi/WifiKeystore.java
index 2ba7468..59f14a9 100644
--- a/wifi/java/src/android/net/wifi/WifiKeystore.java
+++ b/wifi/java/src/android/net/wifi/WifiKeystore.java
@@ -36,6 +36,8 @@
@SuppressLint("UnflaggedApi") // Promoting from @SystemApi(MODULE_LIBRARIES)
public final class WifiKeystore {
private static final String TAG = "WifiKeystore";
+ private static final String sPrimaryDbName =
+ WifiBlobStore.supplicantCanAccessBlobstore() ? "WifiBlobstore" : "LegacyKeystore";
/** @hide */
WifiKeystore() {
@@ -57,8 +59,13 @@
// are able to access the same values.
final long identity = Binder.clearCallingIdentity();
try {
- Log.i(TAG, "put blob. alias " + alias);
- return WifiBlobStore.getInstance().put(alias, blob);
+ Log.i(TAG, "put blob. alias=" + alias + ", primaryDb=" + sPrimaryDbName);
+ if (WifiBlobStore.supplicantCanAccessBlobstore()) {
+ return WifiBlobStore.getInstance().put(alias, blob);
+ } else {
+ WifiBlobStore.getLegacyKeystore().put(alias, Process.WIFI_UID, blob);
+ return true;
+ }
} catch (Exception e) {
Log.e(TAG, "Failed to put blob.", e);
return false;
@@ -80,7 +87,7 @@
public static @NonNull byte[] get(@NonNull String alias) {
final long identity = Binder.clearCallingIdentity();
try {
- Log.i(TAG, "get blob. alias " + alias);
+ Log.i(TAG, "get blob. alias=" + alias + ", primaryDb=" + sPrimaryDbName);
byte[] blob = WifiBlobStore.getInstance().get(alias);
if (blob != null) {
return blob;
@@ -112,7 +119,7 @@
boolean legacyKsSuccess = false;
final long identity = Binder.clearCallingIdentity();
try {
- Log.i(TAG, "remove blob. alias " + alias);
+ Log.i(TAG, "remove blob. alias=" + alias + ", primaryDb=" + sPrimaryDbName);
blobStoreSuccess = WifiBlobStore.getInstance().remove(alias);
// Legacy Keystore will throw an exception if the alias is not found.
WifiBlobStore.getLegacyKeystore().remove(alias, Process.WIFI_UID);
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
index 6ea20ec..7df1d4b 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -577,6 +577,10 @@
@FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static void migrateLegacyKeystoreToWifiBlobstore() {
+ if (!WifiBlobStore.supplicantCanAccessBlobstore()) {
+ Log.i(TAG, "Avoiding migration since supplicant cannot access WifiBlobstore");
+ return;
+ }
final long identity = Binder.clearCallingIdentity();
try {
ILegacyKeystore legacyKeystore = WifiBlobStore.getLegacyKeystore();
diff --git a/wifi/tests/src/android/net/wifi/WifiKeystoreTest.java b/wifi/tests/src/android/net/wifi/WifiKeystoreTest.java
index c28a0ae..4b1dc41 100644
--- a/wifi/tests/src/android/net/wifi/WifiKeystoreTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiKeystoreTest.java
@@ -61,6 +61,7 @@
mSession = ExtendedMockito.mockitoSession()
.mockStatic(WifiBlobStore.class, withSettings().lenient())
.startMocking();
+ when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(true);
when(WifiBlobStore.getLegacyKeystore()).thenReturn(mLegacyKeystore);
when(WifiBlobStore.getInstance()).thenReturn(mWifiBlobStore);
}
@@ -74,16 +75,30 @@
}
/**
- * Test that put() only writes to the WifiBlobStore database.
+ * Test that put() writes to the WifiBlobStore database when it
+ * is available to supplicant.
*/
@Test
- public void testPut() throws Exception {
+ public void testPut_wifiBlobstore() throws Exception {
+ when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(true);
WifiKeystore.put(TEST_ALIAS, TEST_VALUE);
verify(mWifiBlobStore).put(anyString(), any());
verify(mLegacyKeystore, never()).put(anyString(), anyInt(), any());
}
/**
+ * Test that put() writes to Legacy Keystore if the WifiBlobstore database
+ * is not available to supplicant.
+ */
+ @Test
+ public void testPut_legacyKeystore() throws Exception {
+ when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(false);
+ WifiKeystore.put(TEST_ALIAS, TEST_VALUE);
+ verify(mLegacyKeystore).put(anyString(), anyInt(), any());
+ verify(mWifiBlobStore, never()).put(anyString(), any());
+ }
+
+ /**
* Test that if the alias is found in the WifiBlobStore database,
* then the legacy database is not searched.
*/
diff --git a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
index 8a5912f..d95069d 100644
--- a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
@@ -56,6 +56,7 @@
mSession = ExtendedMockito.mockitoSession()
.mockStatic(WifiBlobStore.class, withSettings().lenient())
.startMocking();
+ when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(true);
when(WifiBlobStore.getLegacyKeystore()).thenReturn(mLegacyKeystore);
when(WifiBlobStore.getInstance()).thenReturn(mWifiBlobStore);
when(mLegacyKeystore.get(anyString(), anyInt())).thenReturn(TEST_VALUE);
@@ -70,6 +71,17 @@
}
/**
+ * Verify that the Keystore migration is skipped if supplicant does not have
+ * access to the WifiBlobstore database.
+ */
+ @Test
+ public void testKeystoreMigrationAvoidedOnLegacyVendorPartition() {
+ when(WifiBlobStore.supplicantCanAccessBlobstore()).thenReturn(false);
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
+ }
+
+ /**
* Verify that the Keystore migration method returns immediately if no aliases
* are found in Legacy Keystore.
*/