Merge "Keep disabled resources out of final apk" 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/OWNERS b/OWNERS
index bde7ab2..096da29 100644
--- a/OWNERS
+++ b/OWNERS
@@ -14,6 +14,7 @@
nandana@google.com #{LAST_RESORT_SUGGESTION}
narayan@google.com #{LAST_RESORT_SUGGESTION}
ogunwale@google.com #{LAST_RESORT_SUGGESTION}
+omakoto@google.com #{LAST_RESORT_SUGGESTION}
roosa@google.com #{LAST_RESORT_SUGGESTION}
smoreland@google.com #{LAST_RESORT_SUGGESTION}
yamasani@google.com #{LAST_RESORT_SUGGESTION}
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 87f1124..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"],
@@ -159,7 +245,8 @@
"framework-minus-apex.ravenwood",
],
sdk_version: "core_platform",
- jarjar_rules: ":ravenwood-framework-jarjar-rules",
+ // See b/313930116. Jarjar is too slow on this jar. We use HostStubGen to do the rename.
+ // jarjar_rules: ":ravenwood-framework-jarjar-rules",
}
java_genrule {
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/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 9a178e5..18ee6f2 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -165,6 +165,7 @@
import com.android.server.SystemServiceManager;
import com.android.server.SystemTimeZone;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -3763,8 +3764,10 @@
}
mNextAlarmClockForUser.put(userId, alarmClock);
if (mStartUserBeforeScheduledAlarms) {
- mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
- mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
+ if (shouldAddWakeupForUser(userId)) {
+ mUserWakeupStore.addUserWakeup(userId, convertToElapsed(
+ mNextAlarmClockForUser.get(userId).getTriggerTime(), RTC));
+ }
}
} else {
if (DEBUG_ALARM_CLOCK) {
@@ -3784,6 +3787,23 @@
}
/**
+ * Checks whether the user is of type that needs to be started before the alarm.
+ */
+ @VisibleForTesting
+ boolean shouldAddWakeupForUser(@UserIdInt int userId) {
+ final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+ if (umInternal.getUserInfo(userId) == null || umInternal.getUserInfo(userId).isGuest()) {
+ // Guest user should not be started in the background.
+ return false;
+ } else {
+ // SYSTEM user is always running, so no need to schedule wakeup for it.
+ // Profiles are excluded from the wakeup list because users can explicitly stop them and
+ // so starting them in the background would go against the user's intent.
+ return userId != UserHandle.USER_SYSTEM && umInternal.getUserInfo(userId).isFull();
+ }
+ }
+
+ /**
* Updates NEXT_ALARM_FORMATTED and sends NEXT_ALARM_CLOCK_CHANGED_INTENT for all users
* for which alarm clocks have changed since the last call to this.
*
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
index 93904a7..9fe197d 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/UserWakeupStore.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.os.Environment;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -119,13 +118,10 @@
* @param alarmTime time when alarm is expected to trigger.
*/
public void addUserWakeup(int userId, long alarmTime) {
- // SYSTEM user is always running, so no need to schedule wakeup for it.
- if (userId != UserHandle.USER_SYSTEM) {
- synchronized (mUserWakeupLock) {
- mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
- }
- updateUserListFile();
+ synchronized (mUserWakeupLock) {
+ mUserStarts.put(userId, alarmTime - BUFFER_TIME_MS + getUserWakeupOffset());
}
+ updateUserListFile();
}
/**
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/core/api/current.txt b/core/api/current.txt
index 3c5a31c..ea039a7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43729,6 +43729,7 @@
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT = "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
@@ -59033,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 {
@@ -60098,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/module-lib-current.txt b/core/api/module-lib-current.txt
index e148b5c..df707d1 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -321,7 +321,7 @@
package android.net.wifi {
public final class WifiMigration {
- method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration") public static void migrateLegacyKeystoreToWifiBlobstore();
+ method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration_read_only") public static void migrateLegacyKeystoreToWifiBlobstore();
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bed1b43..445a572 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3448,7 +3448,7 @@
}
public static interface VirtualDeviceManager.ActivityListener {
- method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, int, @Nullable android.content.IntentSender);
method public void onDisplayEmpty(int);
method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
@@ -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.bp b/core/java/Android.bp
index 128fb62..92bca3c 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -22,11 +22,9 @@
":framework-nfc-non-updatable-sources",
":messagequeue-gen",
],
- // Exactly one of the below will be added to srcs by messagequeue-gen
+ // Exactly one MessageQueue.java will be added to srcs by messagequeue-gen
exclude_srcs: [
- "android/os/LegacyMessageQueue/MessageQueue.java",
- "android/os/ConcurrentMessageQueue/MessageQueue.java",
- "android/os/SemiConcurrentMessageQueue/MessageQueue.java",
+ "android/os/*MessageQueue/**/*.java",
],
visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 83c3bf6..89efa9b 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -55,6 +55,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.BiFunction;
/**
@@ -1268,6 +1269,28 @@
public abstract boolean shouldDelayHomeLaunch(int userId);
/**
+ * Used to track when a process is frozen or unfrozen.
+ */
+ public interface FrozenProcessListener {
+ /**
+ * Called when a process is frozen.
+ */
+ void onProcessFrozen(int pid);
+
+ /**
+ * Called when a process is unfrozen.
+ */
+ void onProcessUnfrozen(int pid);
+ }
+
+ /**
+ * Register the frozen process event listener callback. The same listener may be reused for
+ * multiple pids. Listeners are dropped when the process dies.
+ */
+ public abstract void addFrozenProcessListener(int pid, @NonNull Executor executor,
+ @NonNull FrozenProcessListener listener);
+
+ /**
* Add a startup timestamp to the most recent start of the specified process.
*
* @param key The {@link ApplicationStartInfo} start timestamp key of the timestamp to add.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2f80b30..d455853 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -232,6 +232,7 @@
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
+import com.android.internal.os.DebugStore;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
@@ -358,6 +359,15 @@
private static final long BINDER_CALLBACK_THROTTLE = 10_100L;
private long mBinderCallbackLast = -1;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
+ /**
+ * Threshold for identifying long-running looper messages (in milliseconds).
+ * Calculated as 2 seconds multiplied by the hardware timeout multiplier.
+ */
+ private static final long LONG_MESSAGE_THRESHOLD_MS = 2000 * Build.HW_TIMEOUT_MULTIPLIER;
+
/**
* Denotes the sequence number of the process state change for which the main thread needs
* to block until the network rules are updated for it.
@@ -2395,6 +2405,12 @@
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
+ long debugStoreId = -1;
+ // By default, log all long messages when the debug store is enabled,
+ // unless this is overridden for certain message types, for which we have
+ // more granular debug store logging.
+ boolean shouldLogLongMessage = DEBUG_STORE_ENABLED;
+ final long messageStartUptimeMs = SystemClock.uptimeMillis();
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
@@ -2419,24 +2435,61 @@
"broadcastReceiveComp");
}
}
- handleReceiver((ReceiverData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ ReceiverData receiverData = (ReceiverData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordBroadcastHandleReceiver(receiverData.intent);
+ }
+
+ try {
+ handleReceiver(receiverData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case CREATE_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceCreate: " + String.valueOf(msg.obj)));
}
- handleCreateService((CreateServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ CreateServiceData createServiceData = (CreateServiceData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordServiceCreate(createServiceData.info);
+ }
+
+ try {
+ handleCreateService(createServiceData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case BIND_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind: "
+ String.valueOf(msg.obj));
}
- handleBindService((BindServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ BindServiceData bindData = (BindServiceData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordServiceBind(bindData.rebind, bindData.intent);
+ }
+ try {
+ handleBindService(bindData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case UNBIND_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -2452,8 +2505,21 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceStart: " + String.valueOf(msg.obj)));
}
- handleServiceArgs((ServiceArgsData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ ServiceArgsData serviceData = (ServiceArgsData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordServiceOnStart(serviceData.startId,
+ serviceData.flags, serviceData.args);
+ }
+
+ try {
+ handleServiceArgs(serviceData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case STOP_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -2649,11 +2715,17 @@
handleFinishInstrumentationWithoutRestart();
break;
}
+ long messageElapsedTimeMs = SystemClock.uptimeMillis() - messageStartUptimeMs;
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
+ if (shouldLogLongMessage
+ && messageElapsedTimeMs > LONG_MESSAGE_THRESHOLD_MS) {
+ DebugStore.recordLongLooperMessage(msg.what, msg.getTarget().getClass().getName(),
+ messageElapsedTimeMs);
+ }
}
}
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index a07f620..81e9df6 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -16,91 +16,114 @@
package android.app;
+import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
+
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Stores App Compat information about a particular Task.
* @hide
*/
public class AppCompatTaskInfo implements Parcelable {
/**
- * Whether the direct top activity is eligible for letterbox education.
+ * If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
+ * or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public boolean topActivityEligibleForLetterboxEducation;
-
- /**
- * Whether the letterbox education is enabled.
- */
- public boolean isLetterboxEducationEnabled;
-
- /**
- * Whether the direct top activity is in size compat mode on foreground.
- */
- public boolean topActivityInSizeCompat;
-
- /**
- * Whether the double tap is enabled.
- */
- public boolean isLetterboxDoubleTapEnabled;
-
- /**
- * Whether the user aspect ratio settings button is enabled.
- */
- public boolean topActivityEligibleForUserAspectRatioButton;
-
- /**
- * Whether the user has forced the activity to be fullscreen through the user aspect ratio
- * settings.
- */
- public boolean isUserFullscreenOverrideEnabled;
-
- /**
- * Whether the system has forced the activity to be fullscreen
- */
- public boolean isSystemFullscreenOverrideEnabled;
-
- /**
- * Hint about the letterbox state of the top activity.
- */
- public boolean topActivityBoundsLetterboxed;
-
- /**
- * Whether the update comes from a letterbox double-tap action from the user or not.
- */
- public boolean isFromLetterboxDoubleTap;
+ public int topActivityLetterboxVerticalPosition = PROPERTY_VALUE_UNSET;
/**
* If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
* or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxVerticalPosition;
-
- /**
- * If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
- * or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
- */
- public int topActivityLetterboxHorizontalPosition;
+ public int topActivityLetterboxHorizontalPosition = PROPERTY_VALUE_UNSET;
/**
* If {@link #isLetterboxDoubleTapEnabled} it contains the current width of the letterboxed
* activity or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxWidth;
+ public int topActivityLetterboxWidth = PROPERTY_VALUE_UNSET;
/**
* If {@link #isLetterboxDoubleTapEnabled} it contains the current height of the letterboxed
* activity or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxHeight;
+ public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET;
+
+ /**
+ * Contains the current app height of the letterboxed activity if available or
+ * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
+ */
+ public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET;
+
+ /**
+ * Contains the current app width of the letterboxed activity if available or
+ * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
+ */
+ public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;
/**
* Stores camera-related app compat information about a particular Task.
*/
public CameraCompatTaskInfo cameraCompatTaskInfo = CameraCompatTaskInfo.create();
+ /** Constant indicating no top activity flag has been set. */
+ private static final int FLAG_UNDEFINED = 0x0;
+ /** Constant base value for top activity flag. */
+ private static final int FLAG_BASE = 0x1;
+ /** Top activity flag for whether letterbox education is enabled. */
+ private static final int FLAG_LETTERBOX_EDU_ENABLED = FLAG_BASE;
+ /** Top activity flag for whether activity is eligible for letterbox education. */
+ private static final int FLAG_ELIGIBLE_FOR_LETTERBOX_EDU = FLAG_BASE << 1;
+ /** Top activity flag for whether activity bounds are letterboxed. */
+ private static final int FLAG_LETTERBOXED = FLAG_BASE << 2;
+ /** Top activity flag for whether activity is in size compat mode. */
+ private static final int FLAG_IN_SIZE_COMPAT = FLAG_BASE << 3;
+ /** Top activity flag for whether letterbox double tap is enabled. */
+ private static final int FLAG_LETTERBOX_DOUBLE_TAP_ENABLED = FLAG_BASE << 4;
+ /** Top activity flag for whether the update comes from a letterbox double tap action. */
+ private static final int FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP = FLAG_BASE << 5;
+ /** Top activity flag for whether activity is eligible for user aspect ratio button. */
+ private static final int FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON = FLAG_BASE << 6;
+ /** Top activity flag for whether has activity has been overridden to fullscreen by system. */
+ private static final int FLAG_FULLSCREEN_OVERRIDE_SYSTEM = FLAG_BASE << 7;
+ /** Top activity flag for whether has activity has been overridden to fullscreen by user. */
+ private static final int FLAG_FULLSCREEN_OVERRIDE_USER = FLAG_BASE << 8;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ FLAG_UNDEFINED,
+ FLAG_BASE,
+ FLAG_LETTERBOX_EDU_ENABLED,
+ FLAG_ELIGIBLE_FOR_LETTERBOX_EDU,
+ FLAG_LETTERBOXED,
+ FLAG_IN_SIZE_COMPAT,
+ FLAG_LETTERBOX_DOUBLE_TAP_ENABLED,
+ FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP,
+ FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON,
+ FLAG_FULLSCREEN_OVERRIDE_SYSTEM,
+ FLAG_FULLSCREEN_OVERRIDE_USER
+ })
+ public @interface TopActivityFlag {}
+
+ @TopActivityFlag
+ private int mTopActivityFlags;
+
+ @TopActivityFlag
+ private static final int FLAGS_ORGANIZER_INTERESTED = FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP
+ | FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON | FLAG_FULLSCREEN_OVERRIDE_SYSTEM
+ | FLAG_FULLSCREEN_OVERRIDE_USER;
+
+ @TopActivityFlag
+ private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED
+ | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED;
+
private AppCompatTaskInfo() {
// Do nothing
}
@@ -136,9 +159,8 @@
* @return {@code true} if the task has some compat ui.
*/
public boolean hasCompatUI() {
- return topActivityInSizeCompat || topActivityEligibleForLetterboxEducation
- || isLetterboxDoubleTapEnabled
- || topActivityEligibleForUserAspectRatioButton;
+ return isTopActivityInSizeCompat() || eligibleForLetterboxEducation()
+ || isLetterboxDoubleTapEnabled() || eligibleForUserAspectRatioButton();
}
/**
@@ -149,6 +171,142 @@
}
/**
+ * @return {@code true} if the letterbox education is enabled.
+ */
+ public boolean isLetterboxEducationEnabled() {
+ return isTopActivityFlagEnabled(FLAG_LETTERBOX_EDU_ENABLED);
+ }
+
+ /**
+ * Sets the top activity flag for whether letterbox education is enabled.
+ */
+ public void setLetterboxEducationEnabled(boolean enable) {
+ setTopActivityFlag(FLAG_LETTERBOX_EDU_ENABLED, enable);
+ }
+
+ /**
+ * @return {@code true} if the direct top activity is eligible for letterbox education.
+ */
+ public boolean eligibleForLetterboxEducation() {
+ return isTopActivityFlagEnabled(FLAG_ELIGIBLE_FOR_LETTERBOX_EDU);
+ }
+
+ /**
+ * Sets the top activity flag to be eligible for letterbox education.
+ */
+ public void setEligibleForLetterboxEducation(boolean enable) {
+ setTopActivityFlag(FLAG_ELIGIBLE_FOR_LETTERBOX_EDU, enable);
+ }
+
+ /**
+ * @return {@code true} if the direct top activity is eligible for the user aspect ratio
+ * settings button.
+ */
+ public boolean eligibleForUserAspectRatioButton() {
+ return isTopActivityFlagEnabled(FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON);
+ }
+
+ /**
+ * Sets the top activity flag to be eligible for the user aspect ratio settings button.
+ */
+ public void setEligibleForUserAspectRatioButton(boolean enable) {
+ setTopActivityFlag(FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON, enable);
+ }
+
+ /**
+ * @return {@code true} if double tap to reposition letterboxed app is enabled.
+ */
+ public boolean isLetterboxDoubleTapEnabled() {
+ return isTopActivityFlagEnabled(FLAG_LETTERBOX_DOUBLE_TAP_ENABLED);
+ }
+
+ /**
+ * Sets the top activity flag to enable double tap to reposition letterboxed app.
+ */
+ public void setLetterboxDoubleTapEnabled(boolean enable) {
+ setTopActivityFlag(FLAG_LETTERBOX_DOUBLE_TAP_ENABLED, enable);
+ }
+
+ /**
+ * @return {@code true} if the update comes from a letterbox double-tap action from the user.
+ */
+ public boolean isFromLetterboxDoubleTap() {
+ return isTopActivityFlagEnabled(FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP);
+ }
+
+ /**
+ * Sets the top activity flag for whether the update comes from a letterbox double-tap action
+ * from the user.
+ */
+ public void setIsFromLetterboxDoubleTap(boolean enable) {
+ setTopActivityFlag(FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP, enable);
+ }
+
+ /**
+ * @return {@code true} if the user has forced the activity to be fullscreen through the
+ * user aspect ratio settings.
+ */
+ public boolean isUserFullscreenOverrideEnabled() {
+ return isTopActivityFlagEnabled(FLAG_FULLSCREEN_OVERRIDE_USER);
+ }
+
+ /**
+ * Sets the top activity flag for whether the user has forced the activity to be fullscreen
+ * through the user aspect ratio settings.
+ */
+ public void setUserFullscreenOverrideEnabled(boolean enable) {
+ setTopActivityFlag(FLAG_FULLSCREEN_OVERRIDE_USER, enable);
+ }
+
+ /**
+ * @return {@code true} if the system has forced the activity to be fullscreen.
+ */
+ public boolean isSystemFullscreenOverrideEnabled() {
+ return isTopActivityFlagEnabled(FLAG_FULLSCREEN_OVERRIDE_SYSTEM);
+ }
+
+ /**
+ * Sets the top activity flag for whether the system has forced the activity to be fullscreen.
+ */
+ public void setSystemFullscreenOverrideEnabled(boolean enable) {
+ setTopActivityFlag(FLAG_FULLSCREEN_OVERRIDE_SYSTEM, enable);
+ }
+
+ /**
+ * @return {@code true} if the direct top activity is in size compat mode on foreground.
+ */
+ public boolean isTopActivityInSizeCompat() {
+ return isTopActivityFlagEnabled(FLAG_IN_SIZE_COMPAT);
+ }
+
+ /**
+ * Sets the top activity flag for whether the direct top activity is in size compat mode
+ * on foreground.
+ */
+ public void setTopActivityInSizeCompat(boolean enable) {
+ setTopActivityFlag(FLAG_IN_SIZE_COMPAT, enable);
+ }
+
+ /**
+ * @return {@code true} if the top activity bounds are letterboxed.
+ */
+ public boolean isTopActivityLetterboxed() {
+ return isTopActivityFlagEnabled(FLAG_LETTERBOXED);
+ }
+
+ /**
+ * Sets the top activity flag for whether the top activity bounds are letterboxed.
+ */
+ public void setTopActivityLetterboxed(boolean enable) {
+ setTopActivityFlag(FLAG_LETTERBOXED, enable);
+ }
+
+ /** Clear all top activity flags and set to false. */
+ public void clearTopActivityFlags() {
+ mTopActivityFlags = FLAG_UNDEFINED;
+ }
+
+ /**
* @return {@code true} if the app compat parameters that are important for task organizers
* are equal.
*/
@@ -156,16 +314,15 @@
if (that == null) {
return false;
}
- return isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
- && topActivityEligibleForUserAspectRatioButton
- == that.topActivityEligibleForUserAspectRatioButton
+ return (mTopActivityFlags & FLAGS_ORGANIZER_INTERESTED)
+ == (that.mTopActivityFlags & FLAGS_ORGANIZER_INTERESTED)
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
+ && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
+ && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
- && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
- && isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled
&& cameraCompatTaskInfo.equalsForTaskOrganizer(that.cameraCompatTaskInfo);
}
@@ -176,20 +333,15 @@
if (that == null) {
return false;
}
- return topActivityInSizeCompat == that.topActivityInSizeCompat
- && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
- && topActivityEligibleForUserAspectRatioButton
- == that.topActivityEligibleForUserAspectRatioButton
- && topActivityEligibleForLetterboxEducation
- == that.topActivityEligibleForLetterboxEducation
- && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled
+ return (mTopActivityFlags & FLAGS_COMPAT_UI_INTERESTED)
+ == (that.mTopActivityFlags & FLAGS_COMPAT_UI_INTERESTED)
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
- && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
- && isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled
+ && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
+ && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
&& cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo);
}
@@ -197,19 +349,13 @@
* Reads the AppCompatTaskInfo from a parcel.
*/
void readFromParcel(Parcel source) {
- isLetterboxEducationEnabled = source.readBoolean();
- topActivityInSizeCompat = source.readBoolean();
- topActivityEligibleForLetterboxEducation = source.readBoolean();
- isLetterboxDoubleTapEnabled = source.readBoolean();
- topActivityEligibleForUserAspectRatioButton = source.readBoolean();
- topActivityBoundsLetterboxed = source.readBoolean();
- isFromLetterboxDoubleTap = source.readBoolean();
+ mTopActivityFlags = source.readInt();
topActivityLetterboxVerticalPosition = source.readInt();
topActivityLetterboxHorizontalPosition = source.readInt();
topActivityLetterboxWidth = source.readInt();
topActivityLetterboxHeight = source.readInt();
- isUserFullscreenOverrideEnabled = source.readBoolean();
- isSystemFullscreenOverrideEnabled = source.readBoolean();
+ topActivityLetterboxAppWidth = source.readInt();
+ topActivityLetterboxAppHeight = source.readInt();
cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
}
@@ -218,41 +364,43 @@
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeBoolean(isLetterboxEducationEnabled);
- dest.writeBoolean(topActivityInSizeCompat);
- dest.writeBoolean(topActivityEligibleForLetterboxEducation);
- dest.writeBoolean(isLetterboxDoubleTapEnabled);
- dest.writeBoolean(topActivityEligibleForUserAspectRatioButton);
- dest.writeBoolean(topActivityBoundsLetterboxed);
- dest.writeBoolean(isFromLetterboxDoubleTap);
+ dest.writeInt(mTopActivityFlags);
dest.writeInt(topActivityLetterboxVerticalPosition);
dest.writeInt(topActivityLetterboxHorizontalPosition);
dest.writeInt(topActivityLetterboxWidth);
dest.writeInt(topActivityLetterboxHeight);
- dest.writeBoolean(isUserFullscreenOverrideEnabled);
- dest.writeBoolean(isSystemFullscreenOverrideEnabled);
+ dest.writeInt(topActivityLetterboxAppWidth);
+ dest.writeInt(topActivityLetterboxAppHeight);
dest.writeTypedObject(cameraCompatTaskInfo, flags);
}
@Override
public String toString() {
- return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat
- + " topActivityEligibleForLetterboxEducation= "
- + topActivityEligibleForLetterboxEducation
- + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled
- + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
- + " topActivityEligibleForUserAspectRatioButton= "
- + topActivityEligibleForUserAspectRatioButton
- + " topActivityBoundsLetterboxed= " + topActivityBoundsLetterboxed
- + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap
+ return "AppCompatTaskInfo { topActivityInSizeCompat=" + isTopActivityInSizeCompat()
+ + " eligibleForLetterboxEducation= " + eligibleForLetterboxEducation()
+ + " isLetterboxEducationEnabled= " + isLetterboxEducationEnabled()
+ + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled()
+ + " eligibleForUserAspectRatioButton= " + eligibleForUserAspectRatioButton()
+ + " topActivityBoundsLetterboxed= " + isTopActivityLetterboxed()
+ + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap()
+ " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
+ " topActivityLetterboxHorizontalPosition= "
+ topActivityLetterboxHorizontalPosition
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
- + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
- + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled
+ + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth
+ + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight
+ + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
+ + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
+ " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
+ "}";
}
+
+ private void setTopActivityFlag(@TopActivityFlag int flag, boolean enable) {
+ mTopActivityFlags = enable ? (mTopActivityFlags | flag) : (mTopActivityFlags & ~flag);
+ }
+
+ private boolean isTopActivityFlagEnabled(@TopActivityFlag int flag) {
+ return (mTopActivityFlags & flag) == flag;
+ }
}
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/OWNERS b/core/java/android/app/OWNERS
index 1200b4b..adeb045 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -94,6 +94,9 @@
per-file IInstantAppResolver.aidl = file:/services/core/java/com/android/server/pm/OWNERS
per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS
+# Performance
+per-file PropertyInvalidatedCache.java = file:/PERFORMANCE_OWNERS
+
# Pinner
per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 22804a2..0c786cb 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -29,6 +29,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastPrintWriter;
import java.lang.annotation.Retention;
@@ -1260,7 +1261,7 @@
}
public void autoCork() {
- if (Looper.getMainLooper() == null) {
+ if (getLooper() == null) {
// We're not ready to auto-cork yet, so just invalidate the cache immediately.
if (DEBUG) {
Log.w(TAG, "invalidating instead of autocorking early in init: "
@@ -1322,7 +1323,7 @@
@GuardedBy("mLock")
private Handler getHandlerLocked() {
if (mHandler == null) {
- mHandler = new Handler(Looper.getMainLooper()) {
+ mHandler = new Handler(getLooper()) {
@Override
public void handleMessage(Message msg) {
AutoCorker.this.handleMessage(msg);
@@ -1331,6 +1332,14 @@
}
return mHandler;
}
+
+ /**
+ * Return a looper for auto-uncork messages. Messages should be processed on the
+ * background thread, not on the main thread.
+ */
+ private static Looper getLooper() {
+ return BackgroundThread.getHandler().getLooper();
+ }
}
/**
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/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a7070b9..965e3c4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11964,6 +11964,34 @@
}
/**
+ * Adds a user restriction on {@code targetUser}, specified by the {@code key}.
+ *
+ * <p>Called by a system service only, meaning that the caller's UID must be equal to
+ * {@link Process#SYSTEM_UID}.
+ *
+ * @param systemEntity The service entity that adds the restriction. A user restriction set by
+ * a service entity can only be cleared by the same entity. This can be
+ * just the calling package name, or any string of the caller's choice
+ * can be used.
+ * @param key The key of the restriction.
+ * @param targetUser The user to add the restriction on.
+ * @throws SecurityException if the caller is not a system service
+ *
+ * @hide
+ */
+ public void addUserRestriction(@NonNull String systemEntity,
+ @NonNull @UserManager.UserRestrictionKey String key, @UserIdInt int targetUser) {
+ if (mService != null) {
+ try {
+ mService.setUserRestrictionForUser(
+ systemEntity, key, /* enable= */ true, targetUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by a profile owner, device owner or a holder of any permission that is associated with
* a user restriction to set a user restriction specified by the provided {@code key} globally
* on all users. To clear the restriction use {@link #clearUserRestriction}.
@@ -11971,7 +11999,7 @@
* <p>For a given user, a restriction will be set if it was applied globally or locally by any
* admin.
*
- * <p> The calling device admin must be a profile owner, device owner or or a holder of any
+ * <p> The calling device admin must be a profile owner, device owner or a holder of any
* permission that is associated with a user restriction; if it is not, a security
* exception will be thrown.
*
@@ -12072,6 +12100,34 @@
}
/**
+ * Clears a user restriction from {@code targetUser}, specified by the {@code key}.
+ *
+ * <p>Called by a system service only, meaning that the caller's UID must be equal to
+ * {@link Process#SYSTEM_UID}.
+ *
+ * @param systemEntity The system entity that clears the restriction. A user restriction
+ * set by a system entity can only be cleared by the same entity. This
+ * can be just the calling package name, or any string of the caller's
+ * choice can be used.
+ * @param key The key of the restriction.
+ * @param targetUser The user to clear the restriction from.
+ * @throws SecurityException if the caller is not a system service
+ *
+ * @hide
+ */
+ public void clearUserRestriction(@NonNull String systemEntity,
+ @NonNull @UserManager.UserRestrictionKey String key, @UserIdInt int targetUser) {
+ if (mService != null) {
+ try {
+ mService.setUserRestrictionForUser(
+ systemEntity, key, /* enable= */ false, targetUser);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Called by an admin to get user restrictions set by themselves with
* {@link #addUserRestriction(ComponentName, String)}.
* <p>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 381f996..c393a9e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -255,6 +255,7 @@
ComponentName getRestrictionsProvider(int userHandle);
void setUserRestriction(in ComponentName who, in String callerPackage, in String key, boolean enable, boolean parent);
+ void setUserRestrictionForUser(in String systemEntity, in String key, boolean enable, int targetUser);
void setUserRestrictionGlobally(in String callerPackage, in String key);
Bundle getUserRestrictions(in ComponentName who, in String callerPackage, boolean parent);
Bundle getUserRestrictionsGlobally(in String callerPackage);
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/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 1f19f81..933c336 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -132,7 +132,7 @@
* Create a host view. Uses specified animations when pushing
* {@link #updateAppWidget(RemoteViews)}.
*
- * @param animationIn Resource ID of in animation to use
+ * @param animationIn Resource ID of in animation to use
* @param animationOut Resource ID of out animation to use
*/
@SuppressWarnings({"UnusedDeclaration"})
@@ -148,7 +148,7 @@
* Pass the given handler to RemoteViews when updating this widget. Unless this
* is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
* should be made.
- * @param handler
+ *
* @hide
*/
public void setInteractionHandler(InteractionHandler handler) {
@@ -206,10 +206,10 @@
* order for the AppWidgetHost to account for the automatic padding when computing the number
* of cells to allocate to a particular widget.
*
- * @param context the current context
+ * @param context the current context
* @param component the component name of the widget
- * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
- * returned
+ * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
+ * returned
* @return default padding for this widget, in pixels
*/
public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
@@ -291,7 +291,7 @@
}
mDelayedRestoredInflationId = -1;
mDelayedRestoredState = null;
- try {
+ try {
super.dispatchRestoreInstanceState(state);
} catch (Exception e) {
Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
@@ -354,14 +354,14 @@
* the framework will be accounted for automatically. This information gets embedded into the
* AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of
* sizes is explicitly set to an empty list.
- * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
*
* @param newOptions The bundle of options, in addition to the size information,
- * can be null.
- * @param minWidth The minimum width in dips that the widget will be displayed at.
- * @param minHeight The maximum height in dips that the widget will be displayed at.
- * @param maxWidth The maximum width in dips that the widget will be displayed at.
- * @param maxHeight The maximum height in dips that the widget will be displayed at.
+ * can be null.
+ * @param minWidth The minimum width in dips that the widget will be displayed at.
+ * @param minHeight The maximum height in dips that the widget will be displayed at.
+ * @param maxWidth The maximum width in dips that the widget will be displayed at.
+ * @param maxHeight The maximum height in dips that the widget will be displayed at.
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
* @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead.
*/
@Deprecated
@@ -378,12 +378,14 @@
* This method will update the option bundle with the list of sizes and the min/max bounds for
* width and height.
*
- * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
- *
* @param newOptions The bundle of options, in addition to the size information.
- * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider
- * again. Typically, this will be size of the widget in landscape and portrait.
- * On some foldables, this might include the size on the outer and inner screens.
+ * @param sizes Sizes, in dips, the widget may be displayed at without calling the
+ * provider
+ * again. Typically, this will be size of the widget in landscape and
+ * portrait.
+ * On some foldables, this might include the size on the outer and inner
+ * screens.
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
*/
public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<SizeF> sizes) {
AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
@@ -470,9 +472,9 @@
/**
* Specify some extra information for the widget provider. Causes a callback to the
* AppWidgetProvider.
- * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
*
* @param options The bundle of options information.
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
*/
public void updateAppWidgetOptions(Bundle options) {
AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
@@ -507,6 +509,7 @@
/**
* Sets whether the widget is being displayed on a light/white background and use an
* alternate UI if available.
+ *
* @see RemoteViews#setLightBackgroundLayoutId(int)
*/
public void setOnLightBackground(boolean onLightBackground) {
@@ -620,7 +623,7 @@
if (content == null) {
if (mViewMode == VIEW_MODE_ERROR) {
// We've already done this -- nothing to do.
- return ;
+ return;
}
if (exception != null) {
Log.w(TAG, "Error inflating RemoteViews", exception);
@@ -733,7 +736,7 @@
if (adapter instanceof BaseAdapter) {
BaseAdapter baseAdapter = (BaseAdapter) adapter;
baseAdapter.notifyDataSetChanged();
- } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
+ } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
// If the adapter is null, it may mean that the RemoteViewsAapter has not yet
// connected to its associated service, and hence the adapter hasn't been set.
// In this case, we need to defer the notify call until it has been set.
@@ -745,6 +748,7 @@
/**
* Build a {@link Context} cloned into another package name, usually for the
* purposes of reading remote resources.
+ *
* @hide
*/
protected Context getRemoteContextEnsuringCorrectCachedApkPath() {
@@ -760,7 +764,7 @@
}
return newContext;
} catch (NameNotFoundException e) {
- Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found");
+ Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found");
return mContext;
} catch (NullPointerException e) {
Log.e(TAG, "Error trying to create the remote context.", e);
@@ -774,7 +778,7 @@
*/
protected void prepareView(View view) {
// Take requested dimensions from child, but apply default gravity.
- FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
+ FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams) view.getLayoutParams();
if (requested == null) {
requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
@@ -839,7 +843,18 @@
return defaultView;
}
- private void onDefaultViewClicked(View view) {
+ /**
+ * Handles interactions on the default view of the widget. By default does not use the
+ * {@link InteractionHandler} used by other interactions. However, this can be overridden
+ * in order to customize the click behavior.
+ *
+ * @hide
+ */
+ protected void onDefaultViewClicked(@NonNull View view) {
+ final AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
+ if (manager != null) {
+ manager.noteAppWidgetTapped(mAppWidgetId);
+ }
if (mInfo != null) {
LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
List<LauncherActivityInfo> activities = launcherApps.getActivityList(
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 39371a3..564fb02 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -17,6 +17,7 @@
package android.companion.virtual;
import android.content.ComponentName;
+import android.content.IntentSender;
/**
* Interface to listen for activity changes in a virtual device.
@@ -48,6 +49,8 @@
* @param displayId The display ID on which the activity tried to launch.
* @param componentName The component name of the blocked activity.
* @param userId The user ID associated with the blocked activity.
+ * @param intentSender The original sender of the intent.
*/
- void onActivityLaunchBlocked(int displayId, in ComponentName componentName, int userId);
+ void onActivityLaunchBlocked(int displayId, in ComponentName componentName, int userId,
+ in IntentSender intentSender);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d8899b2..19eb497 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -38,6 +38,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplay;
@@ -135,13 +136,14 @@
@Override
public void onActivityLaunchBlocked(int displayId, ComponentName componentName,
- @UserIdInt int userId) {
+ @UserIdInt int userId, IntentSender intentSender) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mActivityListenersLock) {
for (int i = 0; i < mActivityListeners.size(); i++) {
mActivityListeners.valueAt(i)
- .onActivityLaunchBlocked(displayId, componentName, userId);
+ .onActivityLaunchBlocked(
+ displayId, componentName, userId, intentSender);
}
}
} finally {
@@ -593,9 +595,10 @@
}
public void onActivityLaunchBlocked(int displayId, ComponentName componentName,
- @UserIdInt int userId) {
+ @UserIdInt int userId, IntentSender intentSender) {
mExecutor.execute(() ->
- mActivityListener.onActivityLaunchBlocked(displayId, componentName, userId));
+ mActivityListener.onActivityLaunchBlocked(
+ displayId, componentName, userId, intentSender));
}
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 42da7e9..8b60580 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -43,6 +43,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
@@ -837,8 +838,15 @@
* components.</p>
* <p>Any change to the exemptions will only be applied for new activity launches.</p>
*
+ * @param componentName the component name to be exempt from the activity launch policy.
+ * @param displayId the ID of the display, for which to apply the exemption. The display
+ * must belong to the virtual device.
+ * @throws IllegalArgumentException if the specified display does not belong to the virtual
+ * device.
+ *
* @see #removeActivityPolicyExemption
* @see #setDevicePolicy
+ * @see Display#getDisplayId
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -861,8 +869,15 @@
* <p>Note that changing the activity launch policy will clear current set of exempt
* components.</p>
*
+ * @param componentName the component name to be removed from the exemption list.
+ * @param displayId the ID of the display, for which to apply the exemption. The display
+ * must belong to the virtual device.
+ * @throws IllegalArgumentException if the specified display does not belong to the virtual
+ * device.
+ *
* @see #addActivityPolicyExemption
* @see #setDevicePolicy
+ * @see Display#getDisplayId
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -1250,13 +1265,18 @@
* @param displayId The display ID on which the activity tried to launch.
* @param componentName The component name of the blocked activity.
* @param userId The user ID associated with the blocked activity.
+ * @param intentSender The original sender of the intent. May be {@code null} if the sender
+ * expects an activity result to be reported. In that case
+ * {@link android.app.Activity#RESULT_CANCELED} was already reported back because the
+ * launch was blocked. This {@link IntentSender} can be used to relaunch the blocked
+ * activity to a different display.
*
* @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
* @see VirtualDevice#addActivityPolicyExemption(ComponentName)
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
default void onActivityLaunchBlocked(int displayId, @NonNull ComponentName componentName,
- @UserIdInt int userId) {}
+ @UserIdInt int userId, @Nullable IntentSender intentSender) {}
}
/**
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index b29b52d..91586b6 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -81,13 +81,6 @@
}
flag {
- name: "stream_permissions"
- namespace: "virtual_devices"
- description: "Enable streaming permission dialogs to Virtual Devices"
- bug: "291737919"
-}
-
-flag {
name: "persistent_device_id_api"
is_exported: true
namespace: "virtual_devices"
@@ -96,13 +89,6 @@
}
flag {
- name: "express_metrics"
- namespace: "virtual_devices"
- description: "Enable express metrics in VDM"
- bug: "307297730"
-}
-
-flag {
name: "interactive_screen_mirror"
is_exported: true
namespace: "virtual_devices"
@@ -119,17 +105,6 @@
}
flag {
- name: "intercept_intents_before_applying_policy"
- is_exported: true
- namespace: "virtual_devices"
- description: "Apply intent interception before applying activity policy"
- bug: "333444131"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "impulse_velocity_strategy_for_touch_navigation"
is_exported: true
namespace: "virtual_devices"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index a1ae9da..c3c3f0e 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -47,14 +47,6 @@
flag {
namespace: "virtual_devices"
- name: "metrics_collection"
- description: "Enable collection of VDM-related metrics"
- bug: "324842215"
- is_fixed_read_only: true
-}
-
-flag {
- namespace: "virtual_devices"
name: "activity_control_api"
description: "Enable APIs for fine grained activity policy, fallback and callbacks"
bug: "333443509"
@@ -92,27 +84,6 @@
flag {
namespace: "virtual_devices"
- name: "virtual_display_multi_window_mode_support"
- description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default"
- is_fixed_read_only: true
- bug: "341151395"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "virtual_devices"
- name: "intent_interception_action_matching_fix"
- description: "Do not match intents without actions if the filter has actions"
- bug: "343805037"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "virtual_devices"
name: "virtual_display_rotation_api"
description: "API for on-demand rotation of virtual displays"
bug: "291748430"
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index d7195a7..964a8be 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -34,6 +34,8 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.os.DebugStore;
+
/**
* Base class for code that receives and handles broadcast intents sent by
* {@link android.content.Context#sendBroadcast(Intent)}.
@@ -55,6 +57,9 @@
private PendingResult mPendingResult;
private boolean mDebugUnregister;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
/**
* State for a result that is pending for a broadcast receiver. Returned
* by {@link BroadcastReceiver#goAsync() goAsync()}
@@ -255,6 +260,9 @@
"PendingResult#finish#ClassName:" + mReceiverClassName,
1);
}
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordFinish(mReceiverClassName);
+ }
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
@@ -433,7 +441,9 @@
public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
-
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordGoAsync(getClass().getName());
+ }
if (res != null && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
res.mReceiverClassName = getClass().getName();
Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
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/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index d9b0e6d..7c2edd7 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -237,6 +237,8 @@
bug: "307327678"
}
+# This flag is enabled since V but not a MUST requirement in CDD yet, so it needs to stay around
+# for now and any code working with it should keep checking the flag.
flag {
name: "restrict_nonpreloads_system_shareduids"
namespace: "package_manager_service"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 77f2323..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"
@@ -379,3 +389,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "unicorn_mode_refactoring_for_hsum_read_only"
+ namespace: "multiuser"
+ 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/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index e0dc568..914aa51 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,17 +34,22 @@
import android.util.LruCache;
import android.util.Pair;
import android.util.Printer;
+import com.android.internal.util.RingBuffer;
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
+
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
+import java.util.Locale;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;
@@ -185,7 +190,7 @@
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
mPool = pool;
- mRecentOperations = new OperationLog(mPool);
+ mRecentOperations = new OperationLog();
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
@@ -305,6 +310,16 @@
}
}
+ /** Record the start of a transaction for logging and debugging. */
+ void recordBeginTransaction(String mode) {
+ mRecentOperations.beginTransaction(mode);
+ }
+
+ /** Record the end of a transaction for logging and debugging. */
+ void recordEndTransaction(boolean successful) {
+ mRecentOperations.endTransaction(successful);
+ }
+
private void setPageSize() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getDefaultPageSize();
@@ -1335,6 +1350,7 @@
}
printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
+ printer.println(" totalLongOperations: " + mRecentOperations.getTotalLongOperations());
mRecentOperations.dump(printer);
@@ -1593,51 +1609,84 @@
}
}
- private static final class OperationLog {
+ /**
+ * This class implements a leaky bucket strategy to rate-limit operations. A client
+ * accumulates one credit every <n> milliseconds; a credit allows the client execute an
+ * operation (which then deducts the credit). Credits accumulate up to a maximum amount after
+ * which they no longer accumulate. The strategy allows a client to execute an operation
+ * every <n> milliseconds, or to execute a burst, after a period of no operations.
+ */
+ private static class RateLimiter {
+ // When the bucket was created, in ms.
+ private final long mCreationUptimeMs;
+ // The time required to accumulate a single credit.
+ private final long mMsPerCredit;
+ // The maximum number of credits the process can accumulate.
+ private final int mMaxCredits;
+ // Total credits consumed so far.
+ private long mSpent = 0;
+
+ RateLimiter(long msPerCredit, int maxCredits) {
+ mMsPerCredit = msPerCredit;
+ mMaxCredits = maxCredits;
+ mCreationUptimeMs = SystemClock.uptimeMillis() - (mMsPerCredit * mMaxCredits);
+ }
+
+ /** Return true if there is a credit available (and consume that credit). */
+ boolean tryAcquire() {
+ final long now = SystemClock.uptimeMillis();
+ long credits = (now - mCreationUptimeMs) / mMsPerCredit;
+
+ long available = credits - mSpent;
+ if (available > mMaxCredits) {
+ mSpent += available - mMaxCredits;
+ available = credits - mSpent;
+ }
+ if (available > 0) {
+ mSpent++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ private final class OperationLog {
private static final int MAX_RECENT_OPERATIONS = 20;
private static final int COOKIE_GENERATION_SHIFT = 8;
private static final int COOKIE_INDEX_MASK = 0xff;
+ // Operations over 2s are long. Save the last ten.
+ private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
+ private static final int MAX_LONG_OPERATIONS = 10;
+
private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
- private int mIndex;
- private int mGeneration;
- private final SQLiteConnectionPool mPool;
+ private int mIndex = -1;
+ private int mGeneration = 0;
+ private final Operation mTransaction = new Operation();
private long mResultLong = Long.MIN_VALUE;
private String mResultString;
- OperationLog(SQLiteConnectionPool pool) {
- mPool = pool;
- }
+ private final RingBuffer<Operation> mLongOperations =
+ new RingBuffer<>(()->{return new Operation();},
+ (n) ->{return new Operation[n];},
+ MAX_LONG_OPERATIONS);
+ private int mTotalLongOperations = 0;
+
+ // Limit log messages to one every 5 minutes, except that a burst may be 10 messages long.
+ private final RateLimiter mLongLimiter = new RateLimiter(300_000, 10);
public int beginOperation(String kind, String sql, Object[] bindArgs) {
mResultLong = Long.MIN_VALUE;
mResultString = null;
synchronized (mOperations) {
- final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
- Operation operation = mOperations[index];
- if (operation == null) {
- operation = new Operation();
- mOperations[index] = operation;
- } else {
- operation.mFinished = false;
- operation.mException = null;
- if (operation.mBindArgs != null) {
- operation.mBindArgs.clear();
- }
- }
- operation.mStartWallTime = System.currentTimeMillis();
- operation.mStartTime = SystemClock.uptimeMillis();
+ Operation operation = newOperationLocked();
operation.mKind = kind;
operation.mSql = sql;
- operation.mPath = mPool.getPath();
- operation.mResultLong = Long.MIN_VALUE;
- operation.mResultString = null;
if (bindArgs != null) {
if (operation.mBindArgs == null) {
operation.mBindArgs = new ArrayList<Object>();
- } else {
- operation.mBindArgs.clear();
}
for (int i = 0; i < bindArgs.length; i++) {
final Object arg = bindArgs[i];
@@ -1649,16 +1698,45 @@
}
}
}
- operation.mCookie = newOperationCookieLocked(index);
- if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
+ operation.mTraced = Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE);
+ if (operation.mTraced) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- mIndex = index;
return operation.mCookie;
}
}
+ public void beginTransaction(String kind) {
+ synchronized (mOperations) {
+ Operation operation = newOperationLocked();
+ operation.mKind = kind;
+ mTransaction.copyFrom(operation);
+
+ if (operation.mTraced) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
+ operation.mCookie);
+ }
+ }
+ }
+
+ /**
+ * Fetch a new operation from the ring buffer. The operation is properly initialized.
+ * This advances mIndex to point to the next element.
+ */
+ private Operation newOperationLocked() {
+ final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+ Operation operation = mOperations[index];
+ if (operation == null) {
+ mOperations[index] = new Operation();
+ operation = mOperations[index];
+ }
+ operation.start();
+ operation.mCookie = newOperationCookieLocked(index);
+ mIndex = index;
+ return operation;
+ }
+
public void failOperation(int cookie, Exception ex) {
synchronized (mOperations) {
final Operation operation = getOperationLocked(cookie);
@@ -1682,6 +1760,20 @@
}
}
+ public boolean endTransaction(boolean success) {
+ synchronized (mOperations) {
+ mTransaction.mResultLong = success ? 1 : 0;
+ final long execTime = finishOperationLocked(mTransaction);
+ final Operation operation = getOperationLocked(mTransaction.mCookie);
+ if (operation != null) {
+ operation.copyFrom(mTransaction);
+ }
+ mTransaction.setEmpty();
+ return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
+ && SQLiteDebug.shouldLogSlowQuery(execTime);
+ }
+ }
+
public void logOperation(int cookie, String detail) {
synchronized (mOperations) {
logOperationLocked(cookie, detail);
@@ -1699,13 +1791,11 @@
private boolean endOperationDeferLogLocked(int cookie) {
final Operation operation = getOperationLocked(cookie);
if (operation != null) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
+ if (operation.mTraced) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- operation.mEndTime = SystemClock.uptimeMillis();
- operation.mFinished = true;
- final long execTime = operation.mEndTime - operation.mStartTime;
+ final long execTime = finishOperationLocked(operation);
mPool.onStatementExecuted(execTime);
return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
execTime);
@@ -1730,10 +1820,25 @@
return generation << COOKIE_GENERATION_SHIFT | index;
}
+ /** Close out the operation and return the elapsed time. */
+ private long finishOperationLocked(Operation operation) {
+ operation.mEndTime = SystemClock.uptimeMillis();
+ operation.mFinished = true;
+ final long elapsed = operation.mEndTime - operation.mStartTime;
+ if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
+ mLongOperations.getNextSlot().copyFrom(operation);
+ mTotalLongOperations++;
+ if (mLongLimiter.tryAcquire()) {
+ Log.i(TAG, "Long db operation: " + mConfiguration.label);
+ }
+ }
+ return elapsed;
+ }
+
private Operation getOperationLocked(int cookie) {
final int index = cookie & COOKIE_INDEX_MASK;
final Operation operation = mOperations[index];
- return operation.mCookie == cookie ? operation : null;
+ return (operation != null && operation.mCookie == cookie) ? operation : null;
}
public String describeCurrentOperation() {
@@ -1748,48 +1853,87 @@
}
}
- public void dump(Printer printer) {
+ /**
+ * Dump an Operation if it is not in the recent operations list. Return 1 if the
+ * operation was dumped and 0 if not.
+ */
+ private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
+ if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
+ return 0;
+ }
+ pw.println(op.describe(counter));
+ return 1;
+ }
+
+ private void dumpRecentLocked(Printer printer) {
synchronized (mOperations) {
printer.println(" Most recently executed operations:");
int index = mIndex;
- Operation operation = mOperations[index];
- if (operation != null) {
- // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
- // and is relatively expensive to create during preloading. This method is only
- // used when dumping a connection, which is a rare (mainly error) case.
- SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- int n = 0;
- do {
- StringBuilder msg = new StringBuilder();
- msg.append(" ").append(n).append(": [");
- String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
- msg.append(formattedStartTime);
- msg.append("] ");
- operation.describe(msg, false); // Never dump bingargs in a bugreport
- printer.println(msg.toString());
-
- if (index > 0) {
- index -= 1;
- } else {
- index = MAX_RECENT_OPERATIONS - 1;
- }
- n += 1;
- operation = mOperations[index];
- } while (operation != null && n < MAX_RECENT_OPERATIONS);
- } else {
+ if (index == 0) {
printer.println(" <none>");
+ return;
}
+
+ // Operations are dumped in order of most recent first.
+ int counter = 0;
+ int n = 0;
+ Operation operation = mOperations[index];
+ do {
+ printer.println(operation.describe(counter));
+
+ if (index > 0) {
+ index -= 1;
+ } else {
+ index = MAX_RECENT_OPERATIONS - 1;
+ }
+ n++;
+ counter++;
+ operation = mOperations[index];
+ } while (operation != null && n < MAX_RECENT_OPERATIONS);
+ counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
+ }
+ }
+
+ private void dumpLongLocked(Printer printer) {
+ printer.println(" Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
+ if (mLongOperations.isEmpty()) {
+ printer.println(" <none>");
+ return;
+ }
+ Operation[] longOps = mLongOperations.toArray();
+ for (int i = 0; i < longOps.length; i++) {
+ if (longOps[i] != null) {
+ printer.println(longOps[i].describe(i));
+ }
+ }
+ }
+
+ public long getTotalLongOperations() {
+ return mTotalLongOperations;
+ }
+
+ public void dump(Printer printer) {
+ synchronized (mOperations) {
+ dumpRecentLocked(printer);
+ dumpLongLocked(printer);
}
}
}
- private static final class Operation {
+ private final class Operation {
// Trim all SQL statements to 256 characters inside the trace marker.
// This limit gives plenty of context while leaving space for other
// entries in the trace buffer (and ensures atrace doesn't truncate the
// marker for us, potentially losing metadata in the process).
private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
+ // The reserved start time that indicates the Operation is empty.
+ private static final long EMPTY_OPERATION = -1;
+
+ // The formatter for the timestamp.
+ private static final DateTimeFormatter sDateTime =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
+
public long mStartWallTime; // in System.currentTimeMillis()
public long mStartTime; // in SystemClock.uptimeMillis();
public long mEndTime; // in SystemClock.uptimeMillis();
@@ -1799,16 +1943,61 @@
public boolean mFinished;
public Exception mException;
public int mCookie;
- public String mPath;
public long mResultLong; // MIN_VALUE means "value not set".
public String mResultString;
+ public boolean mTraced;
+
+ /** Reset the object to begin a new operation. */
+ void start() {
+ mStartWallTime = System.currentTimeMillis();
+ mStartTime = SystemClock.uptimeMillis();
+ mEndTime = Long.MIN_VALUE;
+ mKind = null;
+ mSql = null;
+ if (mBindArgs != null) mBindArgs.clear();
+ mFinished = false;
+ mException = null;
+ mCookie = -1;
+ mResultLong = Long.MIN_VALUE;
+ mResultString = null;
+ mTraced = false;
+ }
+
+ /**
+ * Initialize from the source object. This is meant to clone the object for use in a
+ * transaction operation. To that end, the local bind args are set to null.
+ */
+ void copyFrom(Operation r) {
+ mStartWallTime = r.mStartWallTime;
+ mStartTime = r.mStartTime;
+ mEndTime = r.mEndTime;
+ mKind = r.mKind;
+ mSql = r.mSql;
+ mBindArgs = null;
+ mFinished = r.mFinished;
+ mException = r.mException;
+ mCookie = r.mCookie;
+ mResultLong = r.mResultLong;
+ mResultString = r.mResultString;
+ mTraced = r.mTraced;
+ }
+
+ /** Mark the operation empty. */
+ void setEmpty() {
+ mStartWallTime = EMPTY_OPERATION;
+ }
+
+ /** Return true if the operation is empty. */
+ boolean isEmpty() {
+ return mStartWallTime == EMPTY_OPERATION;
+ }
public void describe(StringBuilder msg, boolean allowDetailedLog) {
msg.append(mKind);
if (mFinished) {
msg.append(" took ").append(mEndTime - mStartTime).append("ms");
} else {
- msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
+ msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
.append("ms ago");
}
msg.append(" - ").append(getStatus());
@@ -1837,7 +2026,7 @@
}
msg.append("]");
}
- msg.append(", path=").append(mPath);
+ msg.append(", path=").append(mPool.getPath());
if (mException != null) {
msg.append(", exception=\"").append(mException.getMessage()).append("\"");
}
@@ -1849,6 +2038,21 @@
}
}
+ /**
+ * Convert a wall-clock time in milliseconds to logcat format.
+ */
+ private String timeString(long millis) {
+ return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
+ }
+
+ public String describe(int n) {
+ final StringBuilder msg = new StringBuilder();
+ final String start = timeString(mStartWallTime);
+ msg.append(" ").append(n).append(": [").append(start).append("] ");
+ describe(msg, false); // Never dump bindargs in a bugreport
+ return msg.toString();
+ }
+
private String getStatus() {
if (!mFinished) {
return "running";
@@ -1862,7 +2066,6 @@
return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
return methodName;
}
-
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index ad335b6..15d7d66 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1175,7 +1175,7 @@
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
+ ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+ ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
- printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
+ printer.println(" IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase());
if (isCompatibilityWalEnabled) {
printer.println(" Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 7d9f02d..3b14d9d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -312,6 +312,15 @@
cancellationSignal);
}
+ private String modeString(int transactionMode) {
+ switch (transactionMode) {
+ case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE";
+ case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE";
+ case TRANSACTION_MODE_DEFERRED: return "TRANSACTION-DEFERRED";
+ default: return "TRANSACTION";
+ }
+ }
+
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
@@ -321,6 +330,7 @@
if (mTransactionStack == null) {
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
+ mConnection.recordBeginTransaction(modeString(transactionMode));
}
try {
// Set up the transaction such that we can back out safely
@@ -465,6 +475,7 @@
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
+ mConnection.recordEndTransaction(successful);
releaseConnection(); // might throw
}
}
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/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 42f5fc8..9007b62 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -240,18 +240,19 @@
*
* @param logoDescription The logo description text that will be shown on the prompt.
* @return This builder.
- * @throws IllegalArgumentException If logo description is null or exceeds certain character
- * limit.
+ * @throws IllegalArgumentException If logo description is null.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
@RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
@NonNull
public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
- if (logoDescription == null
- || logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
- throw new IllegalArgumentException(
- "Logo description passed in can not be null or exceed "
- + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number.");
+ if (logoDescription == null || logoDescription.isEmpty()) {
+ throw new IllegalArgumentException("Logo description passed in can not be null");
+ }
+ if (logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) {
+ Log.w(TAG,
+ "Logo description passed in exceeds" + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER
+ + " character number and may be truncated.");
}
mPromptInfo.setLogoDescription(logoDescription);
return this;
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/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt
copy to core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
index 9a5c77a..8d44917 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt
+++ b/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
@@ -14,8 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.shared.education
+package android.hardware.input;
-enum class GestureType {
- BACK_GESTURE,
+/** @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/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index bec1c9e..d85e41d 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -25,6 +25,7 @@
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import android.Manifest;
@@ -326,6 +327,15 @@
}
/**
+ * Returns true if the feature flag for touchpad visualizer is enabled.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadVisualizerFeatureFlagEnabled() {
+ return touchpadVisualizer();
+ }
+
+ /**
* Returns true if the touchpad should allow tap dragging.
*
* The returned value only applies to gesture-compatible touchpads.
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/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index dd484f6..e039953 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -124,8 +124,8 @@
static final String XML_ATTR_TIME_IN_BACKGROUND = "time_in_background";
static final String XML_ATTR_TIME_IN_FOREGROUND_SERVICE = "time_in_foreground_service";
- // We need about 700 bytes per UID
- private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 5_000 * 700;
+ // Max window size. CursorWindow uses only as much memory as needed.
+ private static final long BATTERY_CONSUMER_CURSOR_WINDOW_SIZE = 20_000_000; // bytes
private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 72b5cf7..da2eec9 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.os.Handler;
+import android.os.Looper;
import android.os.Trace;
import android.util.Log;
import android.util.Printer;
@@ -214,7 +215,7 @@
private volatile long mNextInsertSeqValue = 0;
/*
* The exception to the FIFO order rule is sendMessageAtFrontOfQueue().
- * Those messages must be in LIFO order - SIGH.
+ * Those messages must be in LIFO order.
* Decrements on each front of queue insert.
*/
private static final VarHandle sNextFrontInsertSeq;
@@ -535,6 +536,7 @@
/* This is only read/written from the Looper thread */
private int mNextPollTimeoutMillis;
private static final AtomicLong mMessagesDelivered = new AtomicLong();
+ private boolean mMessageDirectlyQueued;
private Message nextMessage() {
int i = 0;
@@ -729,6 +731,7 @@
Binder.flushPendingCommands();
}
+ mMessageDirectlyQueued = false;
nativePollOnce(ptr, mNextPollTimeoutMillis);
Message msg = nextMessage();
@@ -841,6 +844,22 @@
+ node.isAsync() + " now: " + SystemClock.uptimeMillis());
}
+ final Looper myLooper = Looper.myLooper();
+ /* If we are running on the looper thread we can add directly to the priority queue */
+ if (myLooper != null && myLooper.getQueue() == this) {
+ node.removeFromStack();
+ insertIntoPriorityQueue(node);
+ /*
+ * We still need to do this even though we are the current thread,
+ * otherwise next() may sleep indefinitely.
+ */
+ if (!mMessageDirectlyQueued) {
+ mMessageDirectlyQueued = true;
+ nativeWake(mPtr);
+ }
+ return true;
+ }
+
while (true) {
StackNode old = (StackNode) sState.getVolatile(this);
boolean wakeNeeded;
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/FileUtils.java b/core/java/android/os/FileUtils.java
index e6b1c07..14005b3 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -54,7 +54,6 @@
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.OsConstants;
import android.system.StructStat;
import android.text.TextUtils;
import android.util.DataUnit;
@@ -1535,7 +1534,6 @@
}
/** {@hide} */
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModeStringToPosix(String mode) {
// Quick check for invalid chars
for (int i = 0; i < mode.length(); i++) {
@@ -1570,7 +1568,6 @@
}
/** {@hide} */
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static String translateModePosixToString(int mode) {
String res = "";
if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1592,7 +1589,6 @@
}
/** {@hide} */
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModePosixToPfd(int mode) {
int res = 0;
if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1617,7 +1613,6 @@
}
/** {@hide} */
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModePfdToPosix(int mode) {
int res = 0;
if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
@@ -1642,7 +1637,6 @@
}
/** {@hide} */
- @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModeAccessToPosix(int mode) {
if (mode == F_OK) {
// There's not an exact mapping, so we attempt a read-only open to
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/LockedMessageQueue/MessageQueue.java b/core/java/android/os/LockedMessageQueue/MessageQueue.java
new file mode 100644
index 0000000..b24e14b
--- /dev/null
+++ b/core/java/android/os/LockedMessageQueue/MessageQueue.java
@@ -0,0 +1,1351 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Handler;
+import android.os.Trace;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Low-level class holding the list of messages to be dispatched by a
+ * {@link Looper}. Messages are not added directly to a MessageQueue,
+ * but rather through {@link Handler} objects associated with the Looper.
+ *
+ * <p>You can retrieve the MessageQueue for the current thread with
+ * {@link Looper#myQueue() Looper.myQueue()}.
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host")
+public final class MessageQueue {
+ private static final String TAG = "LockedMessageQueue";
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
+
+ static final class MessageHeap {
+ static final int MESSAGE_HEAP_INITIAL_SIZE = 16;
+
+ Message[] mHeap = new Message[MESSAGE_HEAP_INITIAL_SIZE];
+ int mNumElements = 0;
+
+ static int parentNodeIdx(int i) {
+ return (i - 1) >>> 1;
+ }
+
+ Message getParentNode(int i) {
+ return mHeap[(i - 1) >>> 1];
+ }
+
+ static int rightNodeIdx(int i) {
+ return 2 * i + 2;
+ }
+
+ Message getRightNode(int i) {
+ return mHeap[2 * i + 2];
+ }
+
+ static int leftNodeIdx(int i) {
+ return 2 * i + 1;
+ }
+
+ Message getLeftNode(int i) {
+ return mHeap[2 * i + 1];
+ }
+
+ int size() {
+ return mHeap.length;
+ }
+
+ int numElements() {
+ return mNumElements;
+ }
+
+ boolean isEmpty() {
+ return mNumElements == 0;
+ }
+
+ Message getMessageAt(int index) {
+ return mHeap[index];
+ }
+
+ /*
+ * Returns:
+ * 0 if x==y.
+ * A value less than 0 if x<y.
+ * A value greater than 0 if x>y.
+ */
+ int compareMessage(Message x, Message y) {
+ int compared = Long.compare(x.when, y.when);
+ if (compared == 0) {
+ compared = Long.compare(x.mInsertSeq, y.mInsertSeq);
+ }
+ return compared;
+ }
+
+ int compareMessageByIdx(int x, int y) {
+ return compareMessage(mHeap[x], mHeap[y]);
+ }
+
+ void swap(int x, int y) {
+ Message tmp = mHeap[x];
+ mHeap[x] = mHeap[y];
+ mHeap[y] = tmp;
+ }
+
+ void siftDown(int i) {
+ int smallest = i;
+ int r, l;
+
+ while (true) {
+ r = rightNodeIdx(i);
+ l = leftNodeIdx(i);
+
+ if (r < mNumElements && compareMessageByIdx(r, smallest) < 0) {
+ smallest = r;
+ }
+
+ if (l < mNumElements && compareMessageByIdx(l, smallest) < 0) {
+ smallest = l;
+ }
+
+ if (smallest != i) {
+ swap(i, smallest);
+ i = smallest;
+ continue;
+ }
+ break;
+ }
+ }
+
+ boolean siftUp(int i) {
+ boolean swapped = false;
+ while (i != 0 && compareMessage(mHeap[i], getParentNode(i)) < 0) {
+ int p = parentNodeIdx(i);
+
+ swap(i, p);
+ swapped = true;
+ i = p;
+ }
+
+ return swapped;
+ }
+
+ void maybeGrowHeap() {
+ if (mNumElements == mHeap.length) {
+ /* Grow by 1.5x */
+ int newSize = mHeap.length + (mHeap.length >>> 1);
+ Message[] newHeap;
+ if (DEBUG) {
+ Log.v(TAG, "maybeGrowHeap mNumElements " + mNumElements + " mHeap.length "
+ + mHeap.length + " newSize " + newSize);
+ }
+
+ newHeap = Arrays.copyOf(mHeap, newSize);
+ mHeap = newHeap;
+ }
+ }
+
+ void add(Message m) {
+ int i;
+
+ maybeGrowHeap();
+
+ i = mNumElements;
+ mNumElements++;
+ mHeap[i] = m;
+
+ siftUp(i);
+ }
+
+ void maybeShrinkHeap() {
+ /* Shrink by 2x */
+ int newSize = mHeap.length >>> 1;
+
+ if (newSize >= MESSAGE_HEAP_INITIAL_SIZE
+ && mNumElements <= newSize) {
+ Message[] newHeap;
+
+ if (DEBUG) {
+ Log.v(TAG, "maybeShrinkHeap mNumElements " + mNumElements + " mHeap.length "
+ + mHeap.length + " newSize " + newSize);
+ }
+
+ newHeap = Arrays.copyOf(mHeap, newSize);
+ mHeap = newHeap;
+ }
+ }
+
+ Message poll() {
+ if (mNumElements > 0) {
+ Message ret = mHeap[0];
+ mNumElements--;
+ mHeap[0] = mHeap[mNumElements];
+ mHeap[mNumElements] = null;
+
+ siftDown(0);
+
+ maybeShrinkHeap();
+ return ret;
+ }
+ return null;
+ }
+
+ Message peek() {
+ if (mNumElements > 0) {
+ return mHeap[0];
+ }
+ return null;
+ }
+
+ private void remove(int i) throws IllegalArgumentException {
+ if (i > mNumElements || mNumElements == 0) {
+ throw new IllegalArgumentException("Index " + i + " out of bounds: "
+ + mNumElements);
+ } else if (i == (mNumElements - 1)) {
+ mHeap[i] = null;
+ mNumElements--;
+ } else {
+ mNumElements--;
+ mHeap[i] = mHeap[mNumElements];
+ mHeap[mNumElements] = null;
+ if (!siftUp(i)) {
+ siftDown(i);
+ }
+ }
+ /* Don't shink here, let the caller do this once it has removed all matching items. */
+ }
+
+ void removeAll() {
+ Message m;
+ for (int i = 0; i < mNumElements; i++) {
+ m = mHeap[i];
+ mHeap[i] = null;
+ m.recycleUnchecked();
+ }
+ mNumElements = 0;
+ maybeShrinkHeap();
+ }
+
+ abstract static class MessageHeapCompare {
+ public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+ Runnable r, long when);
+ }
+
+ boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageHeapCompare compare, boolean removeMatches) {
+ boolean found = false;
+ /*
+ * Walk the heap backwards so we don't have to re-visit an array element due to
+ * sifting
+ */
+ for (int i = mNumElements - 1; i >= 0; i--) {
+ if (compare.compareMessage(mHeap[i], h, what, object, r, when)) {
+ found = true;
+ if (removeMatches) {
+ Message m = mHeap[i];
+ try {
+ remove(i);
+ } catch (IllegalArgumentException e) {
+ Log.wtf(TAG, "Index out of bounds during remove " + e);
+ }
+ m.recycleUnchecked();
+ continue;
+ }
+ break;
+ }
+ }
+ if (found && removeMatches) {
+ maybeShrinkHeap();
+ }
+ return found;
+ }
+
+ /*
+ * Keep this for manual debugging. It's easier to pepper the code with this function
+ * than MessageQueue.dump()
+ */
+ void print() {
+ Log.v(TAG, "heap num elem: " + mNumElements + " mHeap.length " + mHeap.length);
+ for (int i = 0; i < mNumElements; i++) {
+ Log.v(TAG, "[" + i + "]\t" + mHeap[i] + " seq: " + mHeap[i].mInsertSeq + " async: "
+ + mHeap[i].isAsynchronous());
+ }
+ }
+
+ boolean verify(int root) {
+ int r = rightNodeIdx(root);
+ int l = leftNodeIdx(root);
+
+ if (l >= mNumElements && r >= mNumElements) {
+ return true;
+ }
+
+ if (l < mNumElements && compareMessageByIdx(l, root) < 0) {
+ Log.wtf(TAG, "Verify failure: root idx/when: " + root + "/" + mHeap[root].when
+ + " left node idx/when: " + l + "/" + mHeap[l].when);
+ return false;
+ }
+
+ if (r < mNumElements && compareMessageByIdx(r, root) < 0) {
+ Log.wtf(TAG, "Verify failure: root idx/when: " + root + "/" + mHeap[root].when
+ + " right node idx/when: " + r + "/" + mHeap[r].when);
+ return false;
+ }
+
+ if (!verify(r) || !verify(l)) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean checkDanglingReferences(String where) {
+ /* First, let's make sure we didn't leave any dangling references */
+ for (int i = mNumElements; i < mHeap.length; i++) {
+ if (mHeap[i] != null) {
+ Log.wtf(TAG, "[" + where
+ + "] Verify failure: dangling reference found at index "
+ + i + ": " + mHeap[i] + " Async " + mHeap[i].isAsynchronous()
+ + " mNumElements " + mNumElements + " mHeap.length " + mHeap.length);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean verify() {
+ if (!checkDanglingReferences(TAG)) {
+ return false;
+ }
+ return verify(0);
+ }
+ }
+
+ // True if the message queue can be quit.
+ @UnsupportedAppUsage
+ private final boolean mQuitAllowed;
+
+ @UnsupportedAppUsage
+ @SuppressWarnings("unused")
+ private long mPtr; // used by native code
+
+ private final MessageHeap mPriorityQueue = new MessageHeap();
+ private final MessageHeap mAsyncPriorityQueue = new MessageHeap();
+
+ /*
+ * This helps us ensure that messages with the same timestamp are inserted in FIFO order.
+ * Increments on each insert, starting at 0. MessaeHeap.compareMessage() will compare sequences
+ * when delivery timestamps are identical.
+ */
+ private long mNextInsertSeq;
+
+ /*
+ * The exception to the FIFO order rule is sendMessageAtFrontOfQueue().
+ * Those messages must be in LIFO order.
+ * Decrements on each front of queue insert.
+ */
+ private long mNextFrontInsertSeq = -1;
+
+ @UnsupportedAppUsage
+ private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+ private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
+ private IdleHandler[] mPendingIdleHandlers;
+ private boolean mQuitting;
+
+ // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
+ private boolean mBlocked;
+
+ // The next barrier token.
+ // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+ @UnsupportedAppUsage
+ private int mNextBarrierToken;
+
+ private native static long nativeInit();
+ private native static void nativeDestroy(long ptr);
+ @UnsupportedAppUsage
+ private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
+ private native static void nativeWake(long ptr);
+ private native static boolean nativeIsPolling(long ptr);
+ private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
+
+ MessageQueue(boolean quitAllowed) {
+ mQuitAllowed = quitAllowed;
+ mPtr = nativeInit();
+ }
+
+ @GuardedBy("this")
+ private void removeRootFromPriorityQueue(Message msg) {
+ Message tmp;
+ if (msg.isAsynchronous()) {
+ tmp = mAsyncPriorityQueue.poll();
+ } else {
+ tmp = mPriorityQueue.poll();
+ }
+ if (DEBUG && tmp != msg) {
+ Log.wtf(TAG, "Unexpected message at head of heap. Wanted: " + msg + " msg.isAsync "
+ + msg.isAsynchronous() + " Found: " + tmp);
+
+ mPriorityQueue.print();
+ mAsyncPriorityQueue.print();
+ }
+ }
+
+ @GuardedBy("this")
+ private Message pickEarliestMessage(Message x, Message y) {
+ if (x != null && y != null) {
+ if (mPriorityQueue.compareMessage(x, y) < 0) {
+ return x;
+ }
+ return y;
+ }
+
+ return x != null ? x : y;
+ }
+
+ @GuardedBy("this")
+ private Message peekEarliestMessage() {
+ Message x = mPriorityQueue.peek();
+ Message y = mAsyncPriorityQueue.peek();
+
+ return pickEarliestMessage(x, y);
+ }
+
+ @GuardedBy("this")
+ private boolean priorityQueuesAreEmpty() {
+ return mPriorityQueue.isEmpty() && mAsyncPriorityQueue.isEmpty();
+ }
+
+ @GuardedBy("this")
+ private boolean priorityQueueHasBarrier() {
+ Message m = mPriorityQueue.peek();
+
+ if (m != null && m.target == null) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ // Disposes of the underlying message queue.
+ // Must only be called on the looper thread or the finalizer.
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ /**
+ * Returns true if the looper has no pending messages which are due to be processed.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is idle.
+ */
+ public boolean isIdle() {
+ synchronized (this) {
+ Message m = peekEarliestMessage();
+ final long now = SystemClock.uptimeMillis();
+
+ return (priorityQueuesAreEmpty() || now < m.when);
+ }
+ }
+
+ /**
+ * Add a new {@link IdleHandler} to this message queue. This may be
+ * removed automatically for you by returning false from
+ * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+ * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be added.
+ */
+ public void addIdleHandler(@NonNull IdleHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("Can't add a null IdleHandler");
+ }
+ synchronized (this) {
+ mIdleHandlers.add(handler);
+ }
+ }
+
+ /**
+ * Remove an {@link IdleHandler} from the queue that was previously added
+ * with {@link #addIdleHandler}. If the given object is not currently
+ * in the idle list, nothing is done.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be removed.
+ */
+ public void removeIdleHandler(@NonNull IdleHandler handler) {
+ synchronized (this) {
+ mIdleHandlers.remove(handler);
+ }
+ }
+
+ /**
+ * Returns whether this looper's thread is currently polling for more work to do.
+ * This is a good signal that the loop is still alive rather than being stuck
+ * handling a callback. Note that this method is intrinsically racy, since the
+ * state of the loop can change before you get the result back.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is currently polling for events.
+ * @hide
+ */
+ public boolean isPolling() {
+ synchronized (this) {
+ return isPollingLocked();
+ }
+ }
+
+ private boolean isPollingLocked() {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when mQuitting is false.
+ return !mQuitting && nativeIsPolling(mPtr);
+ }
+
+ /**
+ * Adds a file descriptor listener to receive notification when file descriptor
+ * related events occur.
+ * <p>
+ * If the file descriptor has already been registered, the specified events
+ * and listener will replace any that were previously associated with it.
+ * It is not possible to set more than one listener per file descriptor.
+ * </p><p>
+ * It is important to always unregister the listener when the file descriptor
+ * is no longer of use.
+ * </p>
+ *
+ * @param fd The file descriptor for which a listener will be registered.
+ * @param events The set of events to receive: a combination of the
+ * {@link OnFileDescriptorEventListener#EVENT_INPUT},
+ * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and
+ * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested
+ * set of events is zero, then the listener is unregistered.
+ * @param listener The listener to invoke when file descriptor events occur.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #removeOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
+ @OnFileDescriptorEventListener.Events int events,
+ @NonNull OnFileDescriptorEventListener listener) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (this) {
+ updateOnFileDescriptorEventListenerLocked(fd, events, listener);
+ }
+ }
+
+ /**
+ * Removes a file descriptor listener.
+ * <p>
+ * This method does nothing if no listener has been registered for the
+ * specified file descriptor.
+ * </p>
+ *
+ * @param fd The file descriptor whose listener will be unregistered.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #addOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+
+ synchronized (this) {
+ updateOnFileDescriptorEventListenerLocked(fd, 0, null);
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
+ OnFileDescriptorEventListener listener) {
+ final int fdNum = fd.getInt$();
+
+ int index = -1;
+ FileDescriptorRecord record = null;
+ if (mFileDescriptorRecords != null) {
+ index = mFileDescriptorRecords.indexOfKey(fdNum);
+ if (index >= 0) {
+ record = mFileDescriptorRecords.valueAt(index);
+ if (record != null && record.mEvents == events) {
+ return;
+ }
+ }
+ }
+
+ if (events != 0) {
+ events |= OnFileDescriptorEventListener.EVENT_ERROR;
+ if (record == null) {
+ if (mFileDescriptorRecords == null) {
+ mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+ }
+ record = new FileDescriptorRecord(fd, events, listener);
+ mFileDescriptorRecords.put(fdNum, record);
+ } else {
+ record.mListener = listener;
+ record.mEvents = events;
+ record.mSeq += 1;
+ }
+ nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+ } else if (record != null) {
+ record.mEvents = 0;
+ mFileDescriptorRecords.removeAt(index);
+ nativeSetFileDescriptorEvents(mPtr, fdNum, 0);
+ }
+ }
+
+ // Called from native code.
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ private int dispatchEvents(int fd, int events) {
+ // Get the file descriptor record and any state that might change.
+ final FileDescriptorRecord record;
+ final int oldWatchedEvents;
+ final OnFileDescriptorEventListener listener;
+ final int seq;
+ synchronized (this) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no listener registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ listener = record.mListener;
+ seq = record.mSeq;
+ }
+
+ // Invoke the listener outside of the lock.
+ int newWatchedEvents = listener.onFileDescriptorEvents(
+ record.mDescriptor, events);
+ if (newWatchedEvents != 0) {
+ newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
+ }
+
+ // Update the file descriptor record if the listener changed the set of
+ // events to watch and the listener itself hasn't been updated since.
+ if (newWatchedEvents != oldWatchedEvents) {
+ synchronized (this) {
+ int index = mFileDescriptorRecords.indexOfKey(fd);
+ if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+ && record.mSeq == seq) {
+ record.mEvents = newWatchedEvents;
+ if (newWatchedEvents == 0) {
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+ }
+ }
+
+ // Return the new set of events to watch for native code to take care of.
+ return newWatchedEvents;
+ }
+
+ private static final AtomicLong mMessagesDelivered = new AtomicLong();
+
+ @UnsupportedAppUsage
+ Message next() {
+ // Return here if the message loop has already quit and been disposed.
+ // This can happen if the application tries to restart a looper after quit
+ // which is not supported.
+ final long ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
+ int pendingIdleHandlerCount = -1; // -1 only during first iteration
+ int nextPollTimeoutMillis = 0;
+ for (;;) {
+ if (nextPollTimeoutMillis != 0) {
+ Binder.flushPendingCommands();
+ }
+
+ nativePollOnce(ptr, nextPollTimeoutMillis);
+
+ synchronized (this) {
+ // Try to retrieve the next message. Return if found.
+ final long now = SystemClock.uptimeMillis();
+ Message prevMsg = null;
+ Message msg = peekEarliestMessage();
+
+ if (DEBUG && msg != null) {
+ Log.v(TAG, "Next found message " + msg + " isAsynchronous: "
+ + msg.isAsynchronous() + " target " + msg.target);
+ }
+
+ if (msg != null && !msg.isAsynchronous() && msg.target == null) {
+ // Stalled by a barrier. Find the next asynchronous message in the queue.
+ msg = mAsyncPriorityQueue.peek();
+ if (DEBUG) {
+ Log.v(TAG, "Next message was barrier async msg: " + msg);
+ }
+ }
+
+ if (msg != null) {
+ if (now < msg.when) {
+ // Next message is not ready. Set a timeout to wake up when it is ready.
+ nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
+ } else {
+ mBlocked = false;
+ removeRootFromPriorityQueue(msg);
+ if (DEBUG) Log.v(TAG, "Returning message: " + msg);
+ msg.markInUse();
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return msg;
+ }
+ } else {
+ // No more messages.
+ nextPollTimeoutMillis = -1;
+ }
+
+ // Process the quit message now that all pending messages have been handled.
+ if (mQuitting) {
+ dispose();
+ return null;
+ }
+
+ // If first time idle, then get the number of idlers to run.
+ // Idle handles only run if the queue is empty or if the first message
+ // in the queue (possibly a barrier) is due to be handled in the future.
+ Message next = peekEarliestMessage();
+ if (pendingIdleHandlerCount < 0
+ && (next == null || now < next.when)) {
+ pendingIdleHandlerCount = mIdleHandlers.size();
+ }
+ if (pendingIdleHandlerCount <= 0) {
+ // No idle handlers to run. Loop and wait some more.
+ mBlocked = true;
+ continue;
+ }
+
+ if (mPendingIdleHandlers == null) {
+ mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
+ }
+ mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
+ }
+
+ // Run the idle handlers.
+ // We only ever reach this code block during the first iteration.
+ for (int i = 0; i < pendingIdleHandlerCount; i++) {
+ final IdleHandler idler = mPendingIdleHandlers[i];
+ mPendingIdleHandlers[i] = null; // release the reference to the handler
+
+ boolean keep = false;
+ try {
+ keep = idler.queueIdle();
+ } catch (Throwable t) {
+ Log.wtf(TAG, "IdleHandler threw exception", t);
+ }
+
+ if (!keep) {
+ synchronized (this) {
+ mIdleHandlers.remove(idler);
+ }
+ }
+ }
+
+ // Reset the idle handler count to 0 so we do not run them again.
+ pendingIdleHandlerCount = 0;
+
+ // While calling an idle handler, a new message could have been delivered
+ // so go back and look again for a pending message without waiting.
+ nextPollTimeoutMillis = 0;
+ }
+ }
+
+ void quit(boolean safe) {
+ if (!mQuitAllowed) {
+ throw new IllegalStateException("Main thread not allowed to quit.");
+ }
+
+ synchronized (this) {
+ if (mQuitting) {
+ return;
+ }
+ mQuitting = true;
+
+ if (safe) {
+ removeAllFutureMessagesLocked();
+ } else {
+ removeAllMessagesLocked();
+ }
+
+ // We can assume mPtr != 0 because mQuitting was previously false.
+ nativeWake(mPtr);
+ }
+ }
+
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public int postSyncBarrier() {
+ return postSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+ private int postSyncBarrier(long when) {
+ // Enqueue a new sync barrier token.
+ // We don't need to wake the queue because the purpose of a barrier is to stall it.
+ synchronized (this) {
+ final int token = mNextBarrierToken++;
+ final Message msg = Message.obtain();
+ msg.arg1 = token;
+
+ enqueueMessageUnchecked(msg, when);
+ return token;
+ }
+ }
+
+ private class MatchBarrierToken extends MessageHeap.MessageHeapCompare {
+ int mBarrierToken;
+
+ MatchBarrierToken(int token) {
+ super();
+ mBarrierToken = token;
+ }
+
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == null && m.arg1 == mBarrierToken) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ @TestApi
+ public void removeSyncBarrier(int token) {
+ final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
+
+ // Remove a sync barrier token from the queue.
+ // If the queue is no longer stalled by a barrier then wake it.
+ synchronized (this) {
+ boolean removed;
+ Message first = mPriorityQueue.peek();
+
+ removed = mPriorityQueue.findOrRemoveMessages(null, 0, null, null, 0,
+ matchBarrierToken, true);
+ if (removed && first != null) {
+ // If the loop is quitting then it is already awake.
+ // We can assume mPtr != 0 when mQuitting is false.
+ if (first.target == null && first.arg1 == token && !mQuitting) {
+ nativeWake(mPtr);
+ }
+ } else if (!removed) {
+ throw new IllegalStateException("The specified message queue synchronization "
+ + " barrier token has not been posted or has already been removed.");
+ }
+ }
+ }
+
+ boolean enqueueMessage(Message msg, long when) {
+ if (msg.target == null) {
+ throw new IllegalArgumentException("Message must have a target.");
+ }
+
+ return enqueueMessageUnchecked(msg, when);
+ }
+
+ boolean enqueueMessageUnchecked(Message msg, long when) {
+ synchronized (this) {
+ if (mQuitting) {
+ IllegalStateException e = new IllegalStateException(
+ msg.target + " sending message to a Handler on a dead thread");
+ Log.w(TAG, e.getMessage(), e);
+ msg.recycle();
+ return false;
+ }
+
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
+ }
+
+ msg.markInUse();
+ msg.when = when;
+ msg.mInsertSeq = when != 0 ? mNextInsertSeq++ : mNextFrontInsertSeq--;
+ if (DEBUG) Log.v(TAG, "Enqueue message: " + msg);
+ boolean needWake;
+ boolean isBarrier = msg.target == null;
+ Message first = peekEarliestMessage();
+
+ if (priorityQueuesAreEmpty() || when == 0 || when < first.when) {
+ needWake = mBlocked && !isBarrier;
+ } else {
+ Message firstNonAsyncMessage =
+ first.isAsynchronous() ? mPriorityQueue.peek() : first;
+
+ needWake = mBlocked && firstNonAsyncMessage != null
+ && firstNonAsyncMessage.target == null && msg.isAsynchronous();
+ }
+
+ if (msg.isAsynchronous()) {
+ mAsyncPriorityQueue.add(msg);
+ } else {
+ mPriorityQueue.add(msg);
+ }
+
+ // We can assume mPtr != 0 because mQuitting is false.
+ if (needWake) {
+ nativeWake(mPtr);
+ }
+ }
+ return true;
+ }
+
+ @GuardedBy("this")
+ boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageHeap.MessageHeapCompare compare, boolean removeMatches) {
+ boolean found = mPriorityQueue.findOrRemoveMessages(h, what, object, r, when, compare,
+ removeMatches);
+ boolean foundAsync = mAsyncPriorityQueue.findOrRemoveMessages(h, what, object, r, when,
+ compare, removeMatches);
+ return found || foundAsync;
+ }
+
+ private static class MatchHandlerWhatAndObject extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandlerWhatAndObject sMatchHandlerWhatAndObject =
+ new MatchHandlerWhatAndObject();
+
+ boolean hasMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ return findOrRemoveMessages(h, what, object, null, 0, sMatchHandlerWhatAndObject,
+ false);
+ }
+ }
+
+ private static class MatchHandlerWhatAndObjectEquals extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandlerWhatAndObjectEquals sMatchHandlerWhatAndObjectEquals =
+ new MatchHandlerWhatAndObjectEquals();
+ boolean hasEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ return findOrRemoveMessages(h, what, object, null, 0,
+ sMatchHandlerWhatAndObjectEquals, false);
+ }
+ }
+
+ private static class MatchHandlerRunnableAndObject extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandlerRunnableAndObject sMatchHandlerRunnableAndObject =
+ new MatchHandlerRunnableAndObject();
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ boolean hasMessages(Handler h, Runnable r, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ return findOrRemoveMessages(h, -1, object, r, 0, sMatchHandlerRunnableAndObject,
+ false);
+ }
+ }
+
+ private static class MatchHandler extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandler sMatchHandler = new MatchHandler();
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ return findOrRemoveMessages(h, -1, null, null, 0, sMatchHandler, false);
+ }
+ }
+
+ void removeMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ synchronized (this) {
+ findOrRemoveMessages(h, what, object, null, 0, sMatchHandlerWhatAndObject, true);
+ }
+ }
+
+ void removeEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ synchronized (this) {
+ findOrRemoveMessages(h, what, object, null, 0, sMatchHandlerWhatAndObjectEquals, true);
+ }
+ }
+
+ void removeMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+
+ synchronized (this) {
+ findOrRemoveMessages(h, -1, object, r, 0, sMatchHandlerRunnableAndObject, true);
+ }
+ }
+
+ private static class MatchHandlerRunnableAndObjectEquals
+ extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandlerRunnableAndObjectEquals sMatchHandlerRunnableAndObjectEquals =
+ new MatchHandlerRunnableAndObjectEquals();
+ void removeEqualMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+
+ synchronized (this) {
+ findOrRemoveMessages(h, -1, object, r, 0, sMatchHandlerRunnableAndObjectEquals, true);
+ }
+ }
+
+ private static class MatchHandlerAndObject extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandlerAndObject sMatchHandlerAndObject = new MatchHandlerAndObject();
+ void removeCallbacksAndMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ synchronized (this) {
+ findOrRemoveMessages(h, -1, object, null, 0, sMatchHandlerAndObject, true);
+ }
+ }
+
+ private static class MatchHandlerAndObjectEquals extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchHandlerAndObjectEquals sMatchHandlerAndObjectEquals =
+ new MatchHandlerAndObjectEquals();
+ void removeCallbacksAndEqualMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+
+ synchronized (this) {
+ findOrRemoveMessages(h, -1, object, null, 0, sMatchHandlerAndObjectEquals, true);
+ }
+ }
+
+ @GuardedBy("this")
+ private void removeAllMessagesLocked() {
+ mPriorityQueue.removeAll();
+ mAsyncPriorityQueue.removeAll();
+ }
+
+ private static class MatchAllFutureMessages extends MessageHeap.MessageHeapCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.when > when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private static final MatchAllFutureMessages sMatchAllFutureMessages =
+ new MatchAllFutureMessages();
+ @GuardedBy("this")
+ private void removeAllFutureMessagesLocked() {
+ findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(),
+ sMatchAllFutureMessages, true);
+ }
+
+ int dumpPriorityQueue(Printer pw, String prefix, Handler h, MessageHeap priorityQueue) {
+ int n = 0;
+ long now = SystemClock.uptimeMillis();
+ for (int i = 0; i < priorityQueue.numElements(); i++) {
+ Message m = priorityQueue.getMessageAt(i);
+ if (h == null && h == m.target) {
+ pw.println(prefix + "Message " + n + ": " + m.toString(now));
+ n++;
+ }
+ }
+ return n;
+ }
+
+ void dumpPriorityQueue(ProtoOutputStream proto, MessageHeap priorityQueue) {
+ for (int i = 0; i < priorityQueue.numElements(); i++) {
+ Message m = priorityQueue.getMessageAt(i);
+ m.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ }
+ }
+
+ void dump(Printer pw, String prefix, Handler h) {
+ synchronized (this) {
+ pw.println(prefix + "(MessageQueue is using Locked implementation)");
+ long now = SystemClock.uptimeMillis();
+ int n = dumpPriorityQueue(pw, prefix, h, mPriorityQueue);
+ n += dumpPriorityQueue(pw, prefix, h, mAsyncPriorityQueue);
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
+ + ", quitting=" + mQuitting + ")");
+ }
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long messageQueueToken = proto.start(fieldId);
+ synchronized (this) {
+ dumpPriorityQueue(proto, mPriorityQueue);
+ dumpPriorityQueue(proto, mAsyncPriorityQueue);
+ proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked());
+ proto.write(MessageQueueProto.IS_QUITTING, mQuitting);
+ }
+ proto.end(messageQueueToken);
+ }
+
+ /**
+ * Callback interface for discovering when a thread is going to block
+ * waiting for more messages.
+ */
+ public static interface IdleHandler {
+ /**
+ * Called when the message queue has run out of messages and will now
+ * wait for more. Return true to keep your idle handler active, false
+ * to have it removed. This may be called if there are still messages
+ * pending in the queue, but they are all scheduled to be dispatched
+ * after the current time.
+ */
+ boolean queueIdle();
+ }
+
+ /**
+ * A listener which is invoked when file descriptor related events occur.
+ */
+ public interface OnFileDescriptorEventListener {
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for input
+ * operations, such as reading.
+ * <p>
+ * The listener should read all available data from the file descriptor
+ * then return <code>true</code> to keep the listener active or <code>false</code>
+ * to remove the listener.
+ * </p><p>
+ * In the case of a socket, this event may be generated to indicate
+ * that there is at least one incoming connection that the listener
+ * should accept.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_INPUT = 1 << 0;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for output
+ * operations, such as writing.
+ * <p>
+ * The listener should write as much data as it needs. If it could not
+ * write everything at once, then it should return <code>true</code> to
+ * keep the listener active. Otherwise, it should return <code>false</code>
+ * to remove the listener then re-register it later when it needs to write
+ * something else.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_OUTPUT = 1 << 1;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor encountered a
+ * fatal error.
+ * <p>
+ * File descriptor errors can occur for various reasons. One common error
+ * is when the remote peer of a socket or pipe closes its end of the connection.
+ * </p><p>
+ * This event may be generated at any time regardless of whether the
+ * {@link #EVENT_ERROR} event mask was specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_ERROR = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+ EVENT_INPUT,
+ EVENT_OUTPUT,
+ EVENT_ERROR
+ })
+ public @interface Events {}
+
+ /**
+ * Called when a file descriptor receives events.
+ *
+ * @param fd The file descriptor.
+ * @param events The set of events that occurred: a combination of the
+ * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+ * @return The new set of events to watch, or 0 to unregister the listener.
+ *
+ * @see #EVENT_INPUT
+ * @see #EVENT_OUTPUT
+ * @see #EVENT_ERROR
+ */
+ @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
+ }
+
+ private static final class FileDescriptorRecord {
+ public final FileDescriptor mDescriptor;
+ public int mEvents;
+ public OnFileDescriptorEventListener mListener;
+ public int mSeq;
+
+ public FileDescriptorRecord(FileDescriptor descriptor,
+ int events, OnFileDescriptorEventListener listener) {
+ mDescriptor = descriptor;
+ mEvents = events;
+ mListener = listener;
+ }
+ }
+}
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 161951e..a1db9be 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -126,6 +126,10 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
+ /** @hide */
+ @SuppressWarnings("unused")
+ public long mInsertSeq;
+
/*package*/ Bundle data;
@UnsupportedAppUsage
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 6d6757d5..7d3076d 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -106,6 +106,9 @@
# ProfilingService
per-file ProfilingServiceManager.java = file:/PERFORMANCE_OWNERS
+# Performance
+per-file IpcDataCache.java = file:/PERFORMANCE_OWNERS
+
# Memory
per-file OomKillRecord.java = file:/MEMORY_OWNERS
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 464df23..4cc057a 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -340,7 +340,6 @@
return pfd;
}
- @RavenwoodReplace
private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
if ((mode & MODE_WRITE_ONLY) != 0 && (mode & MODE_APPEND) == 0
&& (mode & MODE_TRUNCATE) == 0 && ((mode & MODE_READ_ONLY) == 0)
@@ -364,26 +363,16 @@
}
}
- private static FileDescriptor openInternal$ravenwood(File file, int mode)
- throws FileNotFoundException {
- try {
- return native_open$ravenwood(file, mode);
- } catch (FileNotFoundException e) {
- throw e;
- } catch (IOException e) {
- throw new FileNotFoundException(e.getMessage());
- }
- }
-
@RavenwoodReplace
private static void closeInternal(FileDescriptor fd) {
IoUtils.closeQuietly(fd);
}
private static void closeInternal$ravenwood(FileDescriptor fd) {
- // Desktop JVM doesn't have FileDescriptor.close(), so we'll need to go to the ravenwood
- // side to close it.
- native_close$ravenwood(fd);
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ignored) {
+ }
}
/**
@@ -743,7 +732,6 @@
* Return the total size of the file representing this fd, as determined by
* {@code stat()}. Returns -1 if the fd is not a file.
*/
- @RavenwoodThrow(reason = "Os.readlink() and Os.stat()")
public long getStatSize() {
if (mWrapped != null) {
return mWrapped.getStatSize();
@@ -1277,32 +1265,19 @@
}
}
- // These native methods are currently only implemented by Ravenwood, as it's the only
- // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
- private static native void native_setFdInt$ravenwood(FileDescriptor fd, int fdInt);
- private static native int native_getFdInt$ravenwood(FileDescriptor fd);
- private static native FileDescriptor native_open$ravenwood(File file, int pfdMode)
- throws IOException;
- private static native void native_close$ravenwood(FileDescriptor fd);
+ private static native void setFdInt$ravenwood(FileDescriptor fd, int fdInt);
+ private static native int getFdInt$ravenwood(FileDescriptor fd);
@RavenwoodReplace
private static void setFdInt(FileDescriptor fd, int fdInt) {
fd.setInt$(fdInt);
}
- private static void setFdInt$ravenwood(FileDescriptor fd, int fdInt) {
- native_setFdInt$ravenwood(fd, fdInt);
- }
-
@RavenwoodReplace
private static int getFdInt(FileDescriptor fd) {
return fd.getInt$();
}
- private static int getFdInt$ravenwood(FileDescriptor fd) {
- return native_getFdInt$ravenwood(fd);
- }
-
@RavenwoodReplace
private void setFdOwner(FileDescriptor fd) {
IoUtils.setFdOwner(fd, this);
@@ -1320,7 +1295,6 @@
private int acquireRawFd$ravenwood(FileDescriptor fd) {
// FD owners currently unsupported under Ravenwood; return FD directly
return getFdInt(fd);
-
}
@RavenwoodReplace
diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
index 967332f..79f229a 100644
--- a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
@@ -209,7 +209,7 @@
private volatile long mNextInsertSeqValue = 0;
/*
* The exception to the FIFO order rule is sendMessageAtFrontOfQueue().
- * Those messages must be in LIFO order - SIGH.
+ * Those messages must be in LIFO order.
* Decrements on each front of queue insert.
*/
private static final VarHandle sNextFrontInsertSeq;
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 392b6eb..28f2c25 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2391,10 +2391,15 @@
*/
public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
/**
- * Indicates user operation failed because user is disabled on the device.
+ * Indicates user operation failed because private space is disabled on the device.
* @hide
*/
public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
+ /**
+ * Indicates user operation failed because user is restricted on the device.
+ * @hide
+ */
+ public static final int USER_OPERATION_ERROR_USER_RESTRICTED = 10;
/**
* Result returned from various user operations.
@@ -2413,6 +2418,7 @@
USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
USER_OPERATION_ERROR_DISABLED_USER,
USER_OPERATION_ERROR_PRIVATE_PROFILE,
+ USER_OPERATION_ERROR_USER_RESTRICTED,
})
public @interface UserOperationResult {}
@@ -4818,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 = {
@@ -4838,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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 5ef597d..3fe063d 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -91,6 +91,8 @@
bug: "283989236"
}
+# This flag is enabled since V but not a MUST requirement in CDD yet, so it needs to stay around
+# for now and any code working with it should keep checking the flag.
flag {
name: "signature_permission_allowlist_enabled"
is_fixed_read_only: true
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ee6f43..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>
@@ -5912,6 +5932,14 @@
public static final String SHOW_KEY_PRESSES = "show_key_presses";
/**
+ * Show touchpad input visualization on screen.
+ * 0 = no
+ * 1 = yes
+ * @hide
+ */
+ public static final String TOUCHPAD_VISUALIZER = "touchpad_visualizer";
+
+ /**
* Show rotary input dispatched to focused windows on the screen.
* 0 = no
* 1 = yes
@@ -12535,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
@@ -20150,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
@@ -20197,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 e16a6a1..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;
@@ -211,6 +228,9 @@
SUPPRESSED_EFFECT_SCREEN_OFF | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
| SUPPRESSED_EFFECT_LIGHTS | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_AMBIENT;
+ private static final int LEGACY_SUPPRESSED_EFFECTS =
+ Policy.SUPPRESSED_EFFECT_SCREEN_ON | Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+
// ZenModeConfig XML versions distinguishing key changes.
public static final int XML_VERSION_ZEN_UPGRADE = 8;
public static final int XML_VERSION_MODES_API = 11;
@@ -284,6 +304,7 @@
private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant";
private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin";
+ private static final String RULE_ATT_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects";
private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
@@ -435,7 +456,7 @@
newRule.conditionId = Uri.EMPTY;
newRule.allowManualInvocation = true;
newRule.zenPolicy = getDefaultZenPolicy();
- newRule.pkg = "android";
+ newRule.pkg = PACKAGE_ANDROID;
manualRule = newRule;
}
}
@@ -938,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,
@@ -1015,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))) {
@@ -1039,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");
}
@@ -1170,7 +1210,9 @@
}
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);
}
}
return rt;
@@ -1228,6 +1270,8 @@
}
if (Flags.modesUi()) {
out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin);
+ out.attributeInt(null, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS,
+ rule.legacySuppressedEffects);
}
}
}
@@ -1903,6 +1947,13 @@
ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST))) {
suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
}
+
+ // Restore legacy suppressed effects (obsolete fields which are not in ZenPolicy).
+ // These are deprecated and have no effect on behavior, however apps should get them
+ // back if provided to setNotificationPolicy() earlier.
+ suppressedVisualEffects &= ~LEGACY_SUPPRESSED_EFFECTS;
+ suppressedVisualEffects |=
+ (LEGACY_SUPPRESSED_EFFECTS & manualRule.legacySuppressedEffects);
} else {
if (isAllowConversations()) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
@@ -1996,6 +2047,8 @@
if (policy == null) return;
if (Flags.modesUi()) {
manualRule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+ manualRule.legacySuppressedEffects =
+ LEGACY_SUPPRESSED_EFFECTS & policy.suppressedVisualEffects;
} else {
setAllowAlarms((policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0);
allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
@@ -2520,7 +2573,12 @@
@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)
+ int legacySuppressedEffects;
public ZenRule() { }
@@ -2561,6 +2619,7 @@
}
if (Flags.modesUi()) {
disabledOrigin = source.readInt();
+ legacySuppressedEffects = source.readInt();
}
}
}
@@ -2638,6 +2697,7 @@
}
if (Flags.modesUi()) {
dest.writeInt(disabledOrigin);
+ dest.writeInt(legacySuppressedEffects);
}
}
}
@@ -2686,6 +2746,7 @@
}
if (Flags.modesUi()) {
sb.append(",disabledOrigin=").append(disabledOrigin);
+ sb.append(",legacySuppressedEffects=").append(legacySuppressedEffects);
}
}
@@ -2754,7 +2815,8 @@
if (Flags.modesUi()) {
finalEquals = finalEquals
- && other.disabledOrigin == disabledOrigin;
+ && other.disabledOrigin == disabledOrigin
+ && other.legacySuppressedEffects == legacySuppressedEffects;
}
}
@@ -2769,15 +2831,15 @@
component, configurationActivity, pkg, id, enabler, zenPolicy,
zenDeviceEffects, modified, allowManualInvocation, iconResName,
triggerDescription, type, userModifiedFields,
- zenPolicyUserModifiedFields,
- zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin);
+ zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
+ deletionInstant, disabledOrigin, legacySuppressedEffects);
} else {
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy,
zenDeviceEffects, modified, allowManualInvocation, iconResName,
triggerDescription, type, userModifiedFields,
- zenPolicyUserModifiedFields,
- zenDeviceEffectsUserModifiedFields, deletionInstant);
+ zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
+ deletionInstant);
}
}
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
index 91ef11c..a37e227 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -472,6 +472,7 @@
public static final String FIELD_ICON_RES = "iconResName";
public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
public static final String FIELD_TYPE = "type";
+ public static final String FIELD_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects";
// NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
// Special field to track whether this rule became active or inactive
@@ -567,6 +568,13 @@
if (!Objects.equals(from.iconResName, to.iconResName)) {
addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
}
+ if (android.app.Flags.modesUi()) {
+ if (from.legacySuppressedEffects != to.legacySuppressedEffects) {
+ addField(FIELD_LEGACY_SUPPRESSED_EFFECTS,
+ new FieldDiff<>(from.legacySuppressedEffects,
+ to.legacySuppressedEffects));
+ }
+ }
}
}
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 910c462..2669391 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1240,7 +1240,10 @@
return "invalidState{" + state + "}";
}
- private String peopleTypeToString(@PeopleType int peopleType) {
+ /**
+ * @hide
+ */
+ public static String peopleTypeToString(@PeopleType int peopleType) {
switch (peopleType) {
case PEOPLE_TYPE_ANYONE:
return "anyone";
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/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index a806bd2..121c01b 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -73,7 +73,7 @@
}
// Sort entries by time registered when dumping
// TODO: Or should it sort by name?
- entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue()));
+ entries.sort((o1, o2) -> Long.compare(o1.getValue(), o2.getValue()));
final int size = Math.min(entries.size(), limit);
pw.println("SurfaceControlRegistry");
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 14978ed..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;
@@ -478,6 +479,17 @@
*/
int TRANSIT_SLEEP = 12;
/**
+ * An Activity was going to be visible from back navigation.
+ * @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
* transition types for dealing with special cases. These types are effectively ignored by
* Core and will just be passed along as part of TransitionInfo objects. An example is
@@ -505,6 +517,8 @@
TRANSIT_PIP,
TRANSIT_WAKE,
TRANSIT_SLEEP,
+ TRANSIT_PREPARE_BACK_NAVIGATION,
+ TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION,
TRANSIT_FIRST_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
@@ -1918,6 +1932,8 @@
case TRANSIT_PIP: return "PIP";
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) {
@@ -3460,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
@@ -3532,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,
@@ -3636,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 2ac5873..b281015 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -973,8 +973,12 @@
@GuardedBy("mH")
private void setCurrentRootViewLocked(ViewRootImpl rootView) {
+ final boolean wasEmpty = mCurRootView == null;
mImeDispatcher.switchRootView(mCurRootView, rootView);
mCurRootView = rootView;
+ if (wasEmpty && mCurRootView != null) {
+ mImeDispatcher.updateReceivingDispatcher(mCurRootView.getOnBackInvokedDispatcher());
+ }
}
}
@@ -2883,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>
*
@@ -2891,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(),
@@ -2911,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>
*
@@ -2920,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);
@@ -2942,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.
@@ -2953,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(
@@ -2973,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.
*
@@ -2984,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/Editor.java b/core/java/android/widget/Editor.java
index 8b6ead7..d28c953 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3285,9 +3285,11 @@
.setEnabled(mTextView.canShare())
.setIcon(a.getDrawable(6))
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
+ final String selected = mTextView.getSelectedText();
menu.add(CONTEXT_MENU_GROUP_MISC, TextView.ID_AUTOFILL, menuItemOrderAutofill,
android.R.string.autofill)
- .setEnabled(mTextView.canRequestAutofill())
+ .setEnabled(mTextView.canRequestAutofill()
+ && (selected == null || selected.isEmpty()))
.setOnMenuItemClickListener(mOnContextMenuItemClickListener);
mPreserveSelection = true;
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 9099db8..61ecc62 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,6 +28,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
import static 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;
@@ -936,6 +937,7 @@
private TextPaint mTempTextPaint;
private Object mTempCursor;
+ private Matrix mTempMatrix;
@UnsupportedAppUsage
private BoringLayout.Metrics mBoring;
@@ -2738,6 +2740,8 @@
InputMethodManager imm = getInputMethodManager();
if (imm != null) imm.restartInput(this);
+
+ ensureEditorFocusedNotifiedToHandwritingInitiator();
}
private void setInputTypeFromEditor() {
@@ -7843,6 +7847,20 @@
if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
createEditorIfNeeded();
mEditor.mInputType = type;
+ ensureEditorFocusedNotifiedToHandwritingInitiator();
+ }
+
+ private void ensureEditorFocusedNotifiedToHandwritingInitiator() {
+ if (!initiationWithoutInputConnection() || isHandwritingDelegate()) {
+ return;
+ }
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null) {
+ return;
+ }
+ if (isFocused() && hasWindowFocus() && onCheckIsTextEditor()) {
+ viewRoot.getHandwritingInitiator().onEditorFocused(this);
+ }
}
@Override
@@ -12089,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(
@@ -12098,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(
@@ -14262,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.
@@ -14282,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.
*
@@ -14297,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}
*
@@ -14316,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);
@@ -14421,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)
@@ -14516,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/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index 9cd2a71..a16d537 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -23,15 +23,16 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
-import android.companion.virtualdevice.flags.Flags;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.util.ArraySet;
import java.io.PrintWriter;
import java.util.List;
import java.util.Set;
+import java.util.function.Supplier;
/**
* Abstract class to control the policies of the windows that can be displayed on the virtual
@@ -67,9 +68,7 @@
public DisplayWindowPolicyController() {
synchronized (mSupportedWindowingModes) {
mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- if (Flags.virtualDisplayMultiWindowModeSupport()) {
- mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
- }
+ mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
}
}
@@ -139,10 +138,14 @@
* Returns {@code true} if the given activity can be launched on this virtual display in the
* configuration defined by the rest of the arguments. If the given intent would be intercepted
* by the display owner then this means that the activity cannot be launched.
+ *
+ * The intentSender argument can provide an IntentSender for the original intent to be passed
+ * to any activity listeners, in case the activity cannot be launched.
*/
public abstract boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
@Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
- int launchingFromDisplayId, boolean isNewTask);
+ int launchingFromDisplayId, boolean isNewTask, boolean isResultExpected,
+ @Nullable Supplier<IntentSender> intentSender);
/**
* Returns {@code true} if the given activity can be launched on this virtual display in the
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index ce1f986..771dc7a 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -27,10 +27,12 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
+import android.util.Pair;
import android.view.ViewRootImpl;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -58,7 +60,7 @@
// The handler to run callbacks on. This should be on the same thread
// the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on.
private Handler mHandler;
-
+ private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>();
public ImeOnBackInvokedDispatcher(Handler handler) {
mResultReceiver = new ResultReceiver(handler) {
@Override
@@ -66,11 +68,22 @@
WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher();
if (dispatcher != null) {
receive(resultCode, resultData, dispatcher);
+ } else {
+ mQueuedReceive.add(new Pair<>(resultCode, resultData));
}
}
};
}
+ /** Set receiving dispatcher to consume queued receiving events. */
+ public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) {
+ while (!mQueuedReceive.isEmpty()) {
+ final Pair<Integer, Bundle> queuedMessage = mQueuedReceive.poll();
+ receive(queuedMessage.first, queuedMessage.second, dispatcher);
+ }
+ }
+
+
void setHandler(@NonNull Handler handler) {
mHandler = handler;
}
@@ -198,6 +211,7 @@
}
}
mImeCallbacks.clear();
+ mQueuedReceive.clear();
}
@VisibleForTesting(visibility = PACKAGE)
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 2d5b02a..4f84817 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -31,6 +31,13 @@
}
flag {
+ name: "disable_non_resizable_app_snap_resizing"
+ namespace: "lse_desktop_experience"
+ description: "Stops non-resizable app desktop windows from being snap resized"
+ bug: "325240072"
+}
+
+flag {
name: "enable_desktop_windowing_task_limit"
namespace: "lse_desktop_experience"
description: "Enables a limit on the number of Tasks shown in Desktop Mode"
@@ -204,3 +211,17 @@
description: "Enables the tracking of the status for compat ui elements."
bug: "350953004"
}
+
+flag {
+ name: "enable_desktop_windowing_app_to_web_education"
+ namespace: "lse_desktop_experience"
+ 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 80101af..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"
@@ -94,14 +113,6 @@
}
flag {
- name: "activity_snapshot_by_default"
- namespace: "systemui"
- description: "Enable record activity snapshot by default"
- bug: "259497289"
- is_fixed_read_only: true
-}
-
-flag {
name: "supports_multi_instance_system_ui"
is_exported: true
namespace: "multitasking"
@@ -209,3 +220,13 @@
purpose: PURPOSE_BUGFIX
}
}
+flag {
+ name: "migrate_predictive_back_transition"
+ namespace: "windowing_frontend"
+ description: "Create transition when visibility change from predictive back"
+ bug: "347168362"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4c18bbf..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"
@@ -135,14 +128,3 @@
purpose: PURPOSE_BUGFIX
}
}
-
-flag {
- namespace: "windowing_sdk"
- name: "per_user_display_window_settings"
- description: "Whether to store display window settings per user to avoid conflicts"
- bug: "346668297"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
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/os/BackgroundThread.java b/core/java/com/android/internal/os/BackgroundThread.java
index b75daed..79996e5 100644
--- a/core/java/com/android/internal/os/BackgroundThread.java
+++ b/core/java/com/android/internal/os/BackgroundThread.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.NonNull;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
@@ -53,6 +54,7 @@
}
}
+ @NonNull
public static BackgroundThread get() {
synchronized (BackgroundThread.class) {
ensureThreadLocked();
@@ -60,6 +62,7 @@
}
}
+ @NonNull
public static Handler getHandler() {
synchronized (BackgroundThread.class) {
ensureThreadLocked();
@@ -67,6 +70,7 @@
}
}
+ @NonNull
public static Executor getExecutor() {
synchronized (BackgroundThread.class) {
ensureThreadLocked();
diff --git a/core/java/com/android/internal/os/DebugStore.java b/core/java/com/android/internal/os/DebugStore.java
new file mode 100644
index 0000000..4c45fee
--- /dev/null
+++ b/core/java/com/android/internal/os/DebugStore.java
@@ -0,0 +1,247 @@
+/*
+ * 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.os;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * The DebugStore class provides methods for recording various debug events related to service
+ * lifecycle, broadcast receivers and others.
+ * The DebugStore class facilitates debugging ANR issues by recording time-stamped events
+ * related to service lifecycles, broadcast receivers, and other framework operations. It logs
+ * the start and end times of operations within the ANR timer scope called by framework,
+ * enabling pinpointing of methods and events contributing to ANRs.
+ *
+ * Usage currently includes recording service starts, binds, and asynchronous operations initiated
+ * by broadcast receivers, providing a granular view of system behavior that facilitates
+ * identifying performance bottlenecks and optimizing issue resolution.
+ *
+ * @hide
+ */
+public class DebugStore {
+ private static DebugStoreNative sDebugStoreNative = new DebugStoreNativeImpl();
+
+ @UnsupportedAppUsage
+ @VisibleForTesting
+ public static void setDebugStoreNative(DebugStoreNative nativeImpl) {
+ sDebugStoreNative = nativeImpl;
+ }
+ /**
+ * Records the start of a service.
+ *
+ * @param startId The start ID of the service.
+ * @param flags Additional flags for the service start.
+ * @param intent The Intent associated with the service start.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceOnStart(int startId, int flags, @Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "SvcStart",
+ List.of(
+ "stId",
+ String.valueOf(startId),
+ "flg",
+ Integer.toHexString(flags),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "comp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Records the creation of a service.
+ *
+ * @param serviceInfo Information about the service being created.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceCreate(@Nullable ServiceInfo serviceInfo) {
+ return sDebugStoreNative.beginEvent(
+ "SvcCreate",
+ List.of(
+ "name",
+ Objects.toString(serviceInfo != null ? serviceInfo.name : null),
+ "pkg",
+ Objects.toString(serviceInfo != null ? serviceInfo.packageName : null)));
+ }
+
+ /**
+ * Records the binding of a service.
+ *
+ * @param isRebind Indicates whether the service is being rebound.
+ * @param intent The Intent associated with the service binding.
+ * @return A unique identifier for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceBind(boolean isRebind, @Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "SvcBind",
+ List.of(
+ "rebind",
+ String.valueOf(isRebind),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Records an asynchronous operation initiated by a broadcast receiver through calling GoAsync.
+ *
+ * @param receiverClassName The class name of the broadcast receiver.
+ */
+ @UnsupportedAppUsage
+ public static void recordGoAsync(String receiverClassName) {
+ sDebugStoreNative.recordEvent(
+ "GoAsync",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "rcv",
+ Objects.toString(receiverClassName)));
+ }
+
+ /**
+ * Records the completion of a broadcast operation through calling Finish.
+ *
+ * @param receiverClassName The class of the broadcast receiver that completed the operation.
+ */
+ @UnsupportedAppUsage
+ public static void recordFinish(String receiverClassName) {
+ sDebugStoreNative.recordEvent(
+ "Finish",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "rcv",
+ Objects.toString(receiverClassName)));
+ }
+ /**
+ * Records the completion of a long-running looper message.
+ *
+ * @param messageCode The code representing the type of the message.
+ * @param targetClass The FQN of the class that handled the message.
+ * @param elapsedTimeMs The time that was taken to process the message, in milliseconds.
+ */
+ @UnsupportedAppUsage
+ public static void recordLongLooperMessage(int messageCode, String targetClass,
+ long elapsedTimeMs) {
+ sDebugStoreNative.recordEvent(
+ "LooperMsg",
+ List.of(
+ "code",
+ String.valueOf(messageCode),
+ "trgt",
+ Objects.toString(targetClass),
+ "elapsed",
+ String.valueOf(elapsedTimeMs)));
+ }
+
+
+ /**
+ * Records the reception of a broadcast.
+ *
+ * @param intent The Intent associated with the broadcast.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordBroadcastHandleReceiver(@Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "HandleReceiver",
+ List.of(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp", Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg", Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Ends a previously recorded event.
+ *
+ * @param id The unique ID of the event to be ended.
+ */
+ @UnsupportedAppUsage
+ public static void recordEventEnd(long id) {
+ sDebugStoreNative.endEvent(id, Collections.emptyList());
+ }
+
+ /**
+ * An interface for a class that acts as a wrapper for the static native methods
+ * of the Debug Store.
+ *
+ * It allows us to mock static native methods in our tests and should be removed
+ * once mocking static methods becomes easier.
+ */
+ @VisibleForTesting
+ public interface DebugStoreNative {
+ /**
+ * Begins an event with the given name and attributes.
+ */
+ long beginEvent(String eventName, List<String> attributes);
+ /**
+ * Ends an event with the given ID and attributes.
+ */
+ void endEvent(long id, List<String> attributes);
+ /**
+ * Records an event with the given name and attributes.
+ */
+ void recordEvent(String eventName, List<String> attributes);
+ }
+
+ private static class DebugStoreNativeImpl implements DebugStoreNative {
+ @Override
+ public long beginEvent(String eventName, List<String> attributes) {
+ return DebugStore.beginEventNative(eventName, attributes);
+ }
+
+ @Override
+ public void endEvent(long id, List<String> attributes) {
+ DebugStore.endEventNative(id, attributes);
+ }
+
+ @Override
+ public void recordEvent(String eventName, List<String> attributes) {
+ DebugStore.recordEventNative(eventName, attributes);
+ }
+ }
+
+ private static native long beginEventNative(String eventName, List<String> attributes);
+
+ private static native void endEventNative(long id, List<String> attributes);
+
+ private static native void recordEventNative(String eventName, List<String> attributes);
+}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 2ad6651..c7117e9 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -19,4 +19,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "debug_store_enabled"
+ namespace: "stability"
+ description: "If the debug store is enabled."
+ bug: "314735374"
+ is_fixed_read_only: true
}
\ No newline at end of file
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/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index cf2f202..2d1c2f0 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -3,7 +3,9 @@
per-file ViewPager.java = mount@google.com
# LockSettings related
-per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file LockPatternChecker.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file LockPatternUtils.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file LockPatternView.java = file:/packages/SystemUI/OWNERS
per-file *LockScreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file *LockSettings* = file:/services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 5c2a167..effbbe2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -18,6 +18,11 @@
import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
import java.util.ArrayList;
import java.util.HashSet;
@@ -30,6 +35,9 @@
public class CoreDocument {
ArrayList<Operation> mOperations;
+
+ RootLayoutComponent mRootLayoutComponent = null;
+
RemoteComposeState mRemoteComposeState = new RemoteComposeState();
TimeVariables mTimeVariables = new TimeVariables();
// Semantic version of the document
@@ -81,7 +89,6 @@
public void setHeight(int height) {
this.mHeight = height;
mRemoteComposeState.setWindowHeight(height);
-
}
public RemoteComposeBuffer getBuffer() {
@@ -259,10 +266,43 @@
translateOutput[1] = translateY;
}
+ /**
+ * Returns the list of click areas
+ * @return list of click areas in document coordinates
+ */
public Set<ClickAreaRepresentation> getClickAreas() {
return mClickAreas;
}
+ /**
+ * Returns the root layout component
+ * @return returns the root component if it exists, null otherwise
+ */
+ public RootLayoutComponent getRootLayoutComponent() {
+ return mRootLayoutComponent;
+ }
+
+ /**
+ * Invalidate the document for layout measures. This will trigger a layout remeasure pass.
+ */
+ public void invalidateMeasure() {
+ if (mRootLayoutComponent != null) {
+ mRootLayoutComponent.invalidateMeasure();
+ }
+ }
+
+ /**
+ * Returns the component with the given id
+ * @param id component id
+ * @return the component if it exists, null otherwise
+ */
+ public Component getComponent(int id) {
+ if (mRootLayoutComponent != null) {
+ return mRootLayoutComponent.getComponent(id);
+ }
+ return null;
+ }
+
public interface ClickCallbacks {
void click(int id, String metadata);
}
@@ -354,7 +394,54 @@
public void initFromBuffer(RemoteComposeBuffer buffer) {
mOperations = new ArrayList<Operation>();
buffer.inflateFromBuffer(mOperations);
+ mOperations = inflateComponents(mOperations);
mBuffer = buffer;
+ for (Operation op : mOperations) {
+ if (op instanceof RootLayoutComponent) {
+ mRootLayoutComponent = (RootLayoutComponent) op;
+ break;
+ }
+ }
+ if (mRootLayoutComponent != null) {
+ mRootLayoutComponent.assignIds();
+ }
+ }
+
+ /**
+ * Inflate a component tree
+ * @param operations flat list of operations
+ * @return nested list of operations / components
+ */
+ private ArrayList<Operation> inflateComponents(ArrayList<Operation> operations) {
+ Component currentComponent = null;
+ ArrayList<Component> components = new ArrayList<>();
+ ArrayList<Operation> finalOperationsList = new ArrayList<>();
+ ArrayList<Operation> ops = finalOperationsList;
+
+ for (Operation o : operations) {
+ if (o instanceof ComponentStartOperation) {
+ Component component = (Component) o;
+ component.setParent(currentComponent);
+ components.add(component);
+ currentComponent = component;
+ ops.add(currentComponent);
+ ops = currentComponent.getList();
+ } else if (o instanceof ComponentEnd) {
+ if (currentComponent instanceof LayoutComponent) {
+ ((LayoutComponent) currentComponent).inflate();
+ }
+ components.remove(components.size() - 1);
+ if (!components.isEmpty()) {
+ currentComponent = components.get(components.size() - 1);
+ ops = currentComponent.getList();
+ } else {
+ ops = finalOperationsList;
+ }
+ } else {
+ ops.add(o);
+ }
+ }
+ return ops;
}
/**
@@ -559,6 +646,18 @@
context.loadFloat(RemoteContext.ID_WINDOW_WIDTH, getWidth());
context.loadFloat(RemoteContext.ID_WINDOW_HEIGHT, getHeight());
mRepaintNext = context.updateOps();
+ if (mRootLayoutComponent != null) {
+ if (context.mWidth != mRootLayoutComponent.getWidth()
+ || context.mHeight != mRootLayoutComponent.getHeight()) {
+ mRootLayoutComponent.invalidateMeasure();
+ }
+ if (mRootLayoutComponent.needsMeasure()) {
+ mRootLayoutComponent.layout(context);
+ }
+ if (mRootLayoutComponent.doesNeedsRepaint()) {
+ mRepaintNext = 1;
+ }
+ }
for (Operation op : mOperations) {
// operations will only be executed if no theme is set (ie UNSPECIFIED)
// or the theme is equal as the one passed in argument to paint.
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
index 7cb9a42..4a8b3d7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -37,4 +37,3 @@
*/
String deepToString(String indent);
}
-
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 4b8dbf6..9cb024b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -54,6 +54,21 @@
import com.android.internal.widget.remotecompose.core.operations.TextFromFloat;
import com.android.internal.widget.remotecompose.core.operations.TextMerge;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
@@ -117,6 +132,27 @@
public static final int INTEGER_EXPRESSION = 144;
/////////////////////////////////////////======================
+
+ ////////////////////////////////////////
+ // Layout commands
+ ////////////////////////////////////////
+
+ public static final int LAYOUT_ROOT = 200;
+ public static final int LAYOUT_CONTENT = 201;
+ public static final int LAYOUT_BOX = 202;
+ public static final int LAYOUT_ROW = 203;
+ public static final int LAYOUT_COLUMN = 204;
+ public static final int COMPONENT_START = 2;
+ public static final int COMPONENT_END = 3;
+ public static final int MODIFIER_WIDTH = 16;
+ public static final int MODIFIER_HEIGHT = 67;
+ public static final int MODIFIER_BACKGROUND = 55;
+ public static final int MODIFIER_BORDER = 107;
+ public static final int MODIFIER_PADDING = 58;
+ public static final int MODIFIER_CLIP_RECT = 108;
+ public static final int MODIFIER_ROUNDED_CLIP_RECT = 54;
+ public static final int ANIMATION_SPEC = 14;
+
public static IntMap<CompanionOperation> map = new IntMap<>();
static {
@@ -162,6 +198,26 @@
map.put(DATA_INT, IntegerConstant.COMPANION);
map.put(INTEGER_EXPRESSION, IntegerExpression.COMPANION);
map.put(DATA_BOOLEAN, BooleanConstant.COMPANION);
+
+ // Layout
+
+ map.put(COMPONENT_START, ComponentStart.COMPANION);
+ map.put(COMPONENT_END, ComponentEnd.COMPANION);
+ map.put(ANIMATION_SPEC, AnimationSpec.COMPANION);
+
+ map.put(MODIFIER_WIDTH, WidthModifierOperation.COMPANION);
+ map.put(MODIFIER_HEIGHT, HeightModifierOperation.COMPANION);
+ map.put(MODIFIER_PADDING, PaddingModifierOperation.COMPANION);
+ map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation.COMPANION);
+ map.put(MODIFIER_BORDER, BorderModifierOperation.COMPANION);
+ map.put(MODIFIER_ROUNDED_CLIP_RECT, RoundedClipRectModifierOperation.COMPANION);
+ map.put(MODIFIER_CLIP_RECT, ClipRectModifierOperation.COMPANION);
+
+ map.put(LAYOUT_ROOT, RootLayoutComponent.COMPANION);
+ map.put(LAYOUT_CONTENT, LayoutComponentContent.COMPANION);
+ map.put(LAYOUT_BOX, BoxLayout.COMPANION);
+ map.put(LAYOUT_COLUMN, ColumnLayout.COMPANION);
+ map.put(LAYOUT_ROW, RowLayout.COMPANION);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 6d8a442..665fcb7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -23,6 +23,10 @@
public abstract class PaintContext {
protected RemoteContext mContext;
+ public RemoteContext getContext() {
+ return mContext;
+ }
+
public PaintContext(RemoteContext context) {
this.mContext = context;
}
@@ -31,6 +35,28 @@
this.mContext = context;
}
+ /**
+ * convenience function to call matrixSave()
+ */
+ public void save() {
+ matrixSave();
+ }
+
+ /**
+ * convenience function to call matrixRestore()
+ */
+ public void restore() {
+ matrixRestore();
+ }
+
+ /**
+ * convenience function to call matrixSave()
+ */
+ public void saveLayer(float x, float y, float width, float height) {
+ // TODO
+ matrixSave();
+ }
+
public abstract void drawBitmap(int imageId,
int srcLeft, int srcTop, int srcRight, int srcBottom,
int dstLeft, int dstTop, int dstRight, int dstBottom,
@@ -197,8 +223,49 @@
public abstract void clipPath(int pathId, int regionOp);
/**
+ * Clip based ona round rect
+ * @param width
+ * @param height
+ * @param topStart
+ * @param topEnd
+ * @param bottomStart
+ * @param bottomEnd
+ */
+ public abstract void roundedClipRect(float width, float height,
+ float topStart, float topEnd,
+ float bottomStart, float bottomEnd);
+
+ /**
* Reset the paint
*/
public abstract void reset();
+
+ /**
+ * Returns true if the context is in debug mode
+ *
+ * @return true if in debug mode, false otherwise
+ */
+ public boolean isDebug() {
+ return mContext.isDebug();
+ }
+
+ /**
+ * Returns true if layout animations are enabled
+ *
+ * @return true if animations are enabled, false otherwise
+ */
+ public boolean isAnimationEnabled() {
+ return mContext.isAnimationEnabled();
+ }
+
+ /**
+ * Utility function to log comments
+ *
+ * @param content the content to log
+ */
+ public void log(String content) {
+ System.out.println("[LOG] " + content);
+ }
+
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index 2f3fe57..4a1ccc9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -23,9 +23,11 @@
@Override
public void apply(RemoteContext context) {
- if (context.getMode() == RemoteContext.ContextMode.PAINT
- && context.getPaintContext() != null) {
- paint((PaintContext) context.getPaintContext());
+ if (context.getMode() == RemoteContext.ContextMode.PAINT) {
+ PaintContext paintContext = context.getPaintContext();
+ if (paintContext != null) {
+ paint(paintContext);
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index f5f155e..333951b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -54,6 +54,18 @@
import com.android.internal.widget.remotecompose.core.operations.TextMerge;
import com.android.internal.widget.remotecompose.core.operations.Theme;
import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
@@ -132,8 +144,9 @@
* @param contentDescription content description of the document
* @param capabilities bitmask indicating needed capabilities (unused for now)
*/
- public void header(int width, int height, String contentDescription, long capabilities) {
- Header.COMPANION.apply(mBuffer, width, height, capabilities);
+ public void header(int width, int height, String contentDescription,
+ float density, long capabilities) {
+ Header.COMPANION.apply(mBuffer, width, height, density, capabilities);
int contentDescriptionId = 0;
if (contentDescription != null) {
contentDescriptionId = addText(contentDescription);
@@ -149,7 +162,7 @@
* @param contentDescription content description of the document
*/
public void header(int width, int height, String contentDescription) {
- header(width, height, contentDescription, 0);
+ header(width, height, contentDescription, 1f, 0);
}
/**
@@ -857,7 +870,7 @@
}
/**
- * Sets the clip based on clip rec
+ * Sets the clip based on clip rect
* @param left
* @param top
* @param right
@@ -1074,5 +1087,128 @@
NamedVariable.COLOR_TYPE, name);
}
+ /**
+ * Add a component start tag
+ * @param type type of component
+ * @param id component id
+ */
+ public void addComponentStart(int type, int id) {
+ switch (type) {
+ case ComponentStart.ROOT_LAYOUT: {
+ RootLayoutComponent.COMPANION.apply(mBuffer);
+ } break;
+ case ComponentStart.LAYOUT_CONTENT: {
+ LayoutComponentContent.COMPANION.apply(mBuffer);
+ } break;
+ case ComponentStart.LAYOUT_BOX: {
+ BoxLayout.COMPANION.apply(mBuffer, id, -1,
+ BoxLayout.CENTER, BoxLayout.CENTER);
+ } break;
+ case ComponentStart.LAYOUT_ROW: {
+ RowLayout.COMPANION.apply(mBuffer, id, -1,
+ RowLayout.START, RowLayout.TOP, 0f);
+ } break;
+ case ComponentStart.LAYOUT_COLUMN: {
+ ColumnLayout.COMPANION.apply(mBuffer, id, -1,
+ ColumnLayout.START, ColumnLayout.TOP, 0f);
+ } break;
+ default:
+ ComponentStart.Companion.apply(mBuffer,
+ type, id, 0f, 0f);
+ }
+ }
+
+ /**
+ * Add a component start tag
+ * @param type type of component
+ */
+ public void addComponentStart(int type) {
+ addComponentStart(type, -1);
+ }
+
+ /**
+ * Add a component end tag
+ */
+ public void addComponentEnd() {
+ ComponentEnd.Companion.apply(mBuffer);
+ }
+
+ /**
+ * Add a background modifier of provided color
+ * @param color the color of the background
+ * @param shape the background shape -- SHAPE_RECTANGLE, SHAPE_CIRCLE
+ */
+ public void addModifierBackground(int color, int shape) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ BackgroundModifierOperation.COMPANION.apply(mBuffer, 0f, 0f, 0f, 0f,
+ r, g, b, a, shape);
+ }
+
+ /**
+ * Add a border modifier
+ * @param borderWidth the border width
+ * @param borderRoundedCorner the rounded corner radius if the shape is ROUNDED_RECT
+ * @param color the color of the border
+ * @param shape the shape of the border
+ */
+ public void addModifierBorder(float borderWidth, float borderRoundedCorner,
+ int color, int shape) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ BorderModifierOperation.COMPANION.apply(mBuffer, 0f, 0f, 0f, 0f,
+ borderWidth, borderRoundedCorner, r, g, b, a, shape);
+ }
+
+ /**
+ * Add a padding modifier
+ * @param left left padding
+ * @param top top padding
+ * @param right right padding
+ * @param bottom bottom padding
+ */
+ public void addModifierPadding(float left, float top, float right, float bottom) {
+ PaddingModifierOperation.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
+
+
+ /**
+ * Sets the clip based on rounded clip rect
+ * @param topStart
+ * @param topEnd
+ * @param bottomStart
+ * @param bottomEnd
+ */
+ public void addRoundClipRectModifier(float topStart, float topEnd,
+ float bottomStart, float bottomEnd) {
+ RoundedClipRectModifierOperation.COMPANION.apply(mBuffer,
+ topStart, topEnd, bottomStart, bottomEnd);
+ }
+
+ public void addClipRectModifier() {
+ ClipRectModifierOperation.COMPANION.apply(mBuffer);
+ }
+
+ public void addBoxStart(int componentId, int animationId,
+ int horizontal, int vertical) {
+ BoxLayout.COMPANION.apply(mBuffer, componentId, animationId,
+ horizontal, vertical);
+ }
+
+ public void addRowStart(int componentId, int animationId,
+ int horizontal, int vertical, float spacedBy) {
+ RowLayout.COMPANION.apply(mBuffer, componentId, animationId,
+ horizontal, vertical, spacedBy);
+ }
+
+ public void addColumnStart(int componentId, int animationId,
+ int horizontal, int vertical, float spacedBy) {
+ ColumnLayout.COMPANION.apply(mBuffer, componentId, animationId,
+ horizontal, vertical, spacedBy);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 41eeb5b..893dcce 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -19,6 +19,7 @@
import com.android.internal.widget.remotecompose.core.operations.ShaderData;
import com.android.internal.widget.remotecompose.core.operations.Theme;
import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
/**
* Specify an abstract context used to playback RemoteCompose documents
@@ -35,12 +36,26 @@
ContextMode mMode = ContextMode.UNSET;
boolean mDebug = false;
+
private int mTheme = Theme.UNSPECIFIED;
public float mWidth = 0f;
public float mHeight = 0f;
private float mAnimationTime;
+ private boolean mAnimate = true;
+
+ public Component lastComponent;
+ public long currentTime = 0L;
+
+ public boolean isAnimationEnabled() {
+ return mAnimate;
+ }
+
+ public void setAnimationEnabled(boolean value) {
+ mAnimate = value;
+ }
+
/**
* Load a path under an id.
* Paths can be use in clip drawPath and drawTweenPath
@@ -333,9 +348,11 @@
public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT);
// ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f)
public static final float FLOAT_OFFSET_TO_UTC = Utils.asNan(ID_OFFSET_TO_UTC);
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
+
public abstract void addClickArea(
int id,
int contentDescription,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
index 1f04a44..ccbcdf6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
@@ -13,13 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.documentation;
-package com.android.systemui.activatable
-
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+public interface DocumentationBuilder {
+ void add(String value);
+ Operation operation(String category, int id, String name);
+ Operation wipOperation(String category, int id, String name);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
index 1f04a44..6a98b78 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
@@ -13,13 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.documentation;
-package com.android.systemui.activatable
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+public interface DocumentedCompanionOperation extends CompanionOperation {
+ void documentation(DocumentationBuilder doc);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/Operation.java
new file mode 100644
index 0000000..643b925
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/Operation.java
@@ -0,0 +1,151 @@
+/*
+ * 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.widget.remotecompose.core.documentation;
+
+import java.util.ArrayList;
+
+public class Operation {
+ public static final int LAYOUT = 0;
+ public static final int INT = 0;
+ public static final int FLOAT = 1;
+ public static final int BOOLEAN = 2;
+ public static final int BUFFER = 4;
+ public static final int UTF8 = 5;
+ public static final int BYTE = 6;
+ public static final int VALUE = 7;
+ public static final int LONG = 8;
+
+ String mCategory;
+ int mId;
+ String mName;
+ String mDescription;
+
+ boolean mWIP;
+ String mTextExamples;
+
+ ArrayList<StringPair> mExamples = new ArrayList<>();
+ ArrayList<OperationField> mFields = new ArrayList<>();
+
+ int mExamplesWidth = 100;
+ int mExamplesHeight = 100;
+
+
+ public static String getType(int type) {
+ switch (type) {
+ case (INT): return "INT";
+ case (FLOAT): return "FLOAT";
+ case (BOOLEAN): return "BOOLEAN";
+ case (BUFFER): return "BUFFER";
+ case (UTF8): return "UTF8";
+ case (BYTE): return "BYTE";
+ case (VALUE): return "VALUE";
+ case (LONG): return "LONG";
+ }
+ return "UNKNOWN";
+ }
+
+ public Operation(String category, int id, String name, boolean wip) {
+ mCategory = category;
+ mId = id;
+ mName = name;
+ mWIP = wip;
+ }
+
+ public Operation(String category, int id, String name) {
+ this(category, id, name, false);
+ }
+
+ public ArrayList<OperationField> getFields() {
+ return mFields;
+ }
+
+ public String getCategory() {
+ return mCategory;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public boolean isWIP() {
+ return mWIP;
+ }
+
+ public int getSizeFields() {
+ int size = 0;
+ for (OperationField field : mFields) {
+ size += field.getSize();
+ }
+ return size;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public String getTextExamples() {
+ return mTextExamples;
+ }
+
+ public ArrayList<StringPair> getExamples() {
+ return mExamples;
+ }
+
+ public int getExamplesWidth() {
+ return mExamplesWidth;
+ }
+
+ public int getExamplesHeight() {
+ return mExamplesHeight;
+ }
+
+ public Operation field(int type, String name, String description) {
+ mFields.add(new OperationField(type, name, description));
+ return this;
+ }
+
+ public Operation possibleValues(String name, int value) {
+ if (!mFields.isEmpty()) {
+ mFields.get(mFields.size() - 1).possibleValue(name, "" + value);
+ }
+ return this;
+ }
+
+ public Operation description(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ public Operation examples(String examples) {
+ mTextExamples = examples;
+ return this;
+ }
+
+ public Operation exampleImage(String name, String imagePath) {
+ mExamples.add(new StringPair(name, imagePath));
+ return this;
+ }
+
+ public Operation examplesDimension(int width, int height) {
+ mExamplesWidth = width;
+ mExamplesHeight = height;
+ return this;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
new file mode 100644
index 0000000..fc73f4ed6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
@@ -0,0 +1,58 @@
+/*
+ * 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.widget.remotecompose.core.documentation;
+
+import java.util.ArrayList;
+
+public class OperationField {
+ int mType;
+ String mName;
+ String mDescription;
+ ArrayList<StringPair> mPossibleValues = new ArrayList<>();
+
+ public OperationField(int type, String name, String description) {
+ mType = type;
+ mName = name;
+ mDescription = description;
+ }
+ public int getType() {
+ return mType;
+ }
+ public String getName() {
+ return mName;
+ }
+ public String getDescription() {
+ return mDescription;
+ }
+ public ArrayList<StringPair> getPossibleValues() {
+ return mPossibleValues;
+ }
+ public void possibleValue(String name, String value) {
+ mPossibleValues.add(new StringPair(name, value));
+ }
+ public boolean hasEnumeratedValues() {
+ return !mPossibleValues.isEmpty();
+ }
+ public int getSize() {
+ switch (mType) {
+ case (Operation.BYTE) : return 1;
+ case (Operation.INT) : return 4;
+ case (Operation.FLOAT) : return 4;
+ case (Operation.LONG) : return 8;
+ default : return 0;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
index 1f04a44..787bb54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
@@ -13,13 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.documentation;
+public class StringPair {
+ String mName;
+ String mValue;
-package com.android.systemui.activatable
+ StringPair(String name, String value) {
+ mName = name;
+ mValue = value;
+ }
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+ public String getName() {
+ return mName;
+ }
+ public String getValue() {
+ return mValue;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
index ec35a16..53a3aa9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -41,10 +41,10 @@
}
};
protected String mName = "DrawRectBase";
- float mX1;
- float mY1;
- float mX2;
- float mY2;
+ protected float mX1;
+ protected float mY1;
+ protected float mX2;
+ protected float mY2;
float mX1Value;
float mY1Value;
float mX2Value;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index aabed15..9a1f37b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -15,12 +15,16 @@
*/
package com.android.internal.widget.remotecompose.core.operations;
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.LONG;
+
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
import java.util.List;
@@ -41,6 +45,8 @@
int mWidth;
int mHeight;
+
+ float mDensity;
long mCapabilities;
public static final Companion COMPANION = new Companion();
@@ -54,21 +60,23 @@
* @param patchVersion the patch version of the RemoteCompose document API
* @param width the width of the RemoteCompose document
* @param height the height of the RemoteCompose document
+ * @param density the density at which the document was originally created
* @param capabilities bitmask field storing needed capabilities (unused for now)
*/
public Header(int majorVersion, int minorVersion, int patchVersion,
- int width, int height, long capabilities) {
+ int width, int height, float density, long capabilities) {
this.mMajorVersion = majorVersion;
this.mMinorVersion = minorVersion;
this.mPatchVersion = patchVersion;
this.mWidth = width;
this.mHeight = height;
+ this.mDensity = density;
this.mCapabilities = capabilities;
}
@Override
public void write(WireBuffer buffer) {
- COMPANION.apply(buffer, mWidth, mHeight, mCapabilities);
+ COMPANION.apply(buffer, mWidth, mHeight, mDensity, mCapabilities);
}
@Override
@@ -88,7 +96,7 @@
return toString();
}
- public static class Companion implements CompanionOperation {
+ public static class Companion implements DocumentedCompanionOperation {
private Companion() {
}
@@ -102,13 +110,15 @@
return Operations.HEADER;
}
- public void apply(WireBuffer buffer, int width, int height, long capabilities) {
+ public void apply(WireBuffer buffer, int width, int height,
+ float density, long capabilities) {
buffer.start(Operations.HEADER);
buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
buffer.writeInt(width);
buffer.writeInt(height);
+ // buffer.writeFloat(density);
buffer.writeLong(capabilities);
}
@@ -119,10 +129,26 @@
int patchVersion = buffer.readInt();
int width = buffer.readInt();
int height = buffer.readInt();
+ // float density = buffer.readFloat();
+ float density = 1f;
long capabilities = buffer.readLong();
Header header = new Header(majorVersion, minorVersion, patchVersion,
- width, height, capabilities);
+ width, height, density, capabilities);
operations.add(header);
}
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Protocol Operations", id(), name())
+ .description("Document metadata, containing the version,"
+ + " original size & density, capabilities mask")
+ .field(INT, "MAJOR_VERSION", "Major version")
+ .field(INT, "MINOR_VERSION", "Minor version")
+ .field(INT, "PATCH_VERSION", "Patch version")
+ .field(INT, "WIDTH", "Major version")
+ .field(INT, "HEIGHT", "Major version")
+ // .field(FLOAT, "DENSITY", "Major version")
+ .field(LONG, "CAPABILITIES", "Major version");
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index cbe9c12..f982997 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -15,12 +15,15 @@
*/
package com.android.internal.widget.remotecompose.core.operations;
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
import java.util.List;
@@ -70,12 +73,12 @@
return indent + toString();
}
- public static class Companion implements CompanionOperation {
+ public static class Companion implements DocumentedCompanionOperation {
private Companion() {}
@Override
public String name() {
- return "SetTheme";
+ return "Theme";
}
@Override
@@ -93,5 +96,15 @@
int theme = buffer.readInt();
operations.add(new Theme(theme));
}
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Protocol Operations", id(), name())
+ .description("Set a theme")
+ .field(INT, "THEME", "theme id")
+ .possibleValues("UNSPECIFIED", Theme.UNSPECIFIED)
+ .possibleValues("DARK", Theme.DARK)
+ .possibleValues("LIGHT", Theme.LIGHT);
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
new file mode 100644
index 0000000..ee2e11b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -0,0 +1,473 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimateMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.ArrayList;
+
+/**
+ * Generic Component class
+ */
+public class Component extends PaintOperation implements Measurable {
+
+ protected int mComponentId = -1;
+ protected float mX;
+ protected float mY;
+ protected float mWidth;
+ protected float mHeight;
+ protected Component mParent;
+ protected int mAnimationId = -1;
+ public Visibility mVisibility = Visibility.VISIBLE;
+ public ArrayList<Operation> mList = new ArrayList<>();
+ public PaintOperation mPreTranslate;
+ public boolean mNeedsMeasure = true;
+ public boolean mNeedsRepaint = false;
+ public AnimateMeasure mAnimateMeasure;
+ public AnimationSpec mAnimationSpec = new AnimationSpec();
+ public boolean mFirstLayout = true;
+ PaintBundle mPaint = new PaintBundle();
+
+ public ArrayList<Operation> getList() {
+ return mList;
+ }
+ public float getX() {
+ return mX;
+ }
+ public float getY() {
+ return mY;
+ }
+ public float getWidth() {
+ return mWidth;
+ }
+ public float getHeight() {
+ return mHeight;
+ }
+ public int getComponentId() {
+ return mComponentId;
+ }
+
+ public int getAnimationId() {
+ return mAnimationId;
+ }
+
+ public Component getParent() {
+ return mParent;
+ }
+ public void setX(float value) {
+ mX = value;
+ }
+ public void setY(float value) {
+ mY = value;
+ }
+ public void setWidth(float value) {
+ mWidth = value;
+ }
+ public void setHeight(float value) {
+ mHeight = value;
+ }
+
+ public void setComponentId(int id) {
+ mComponentId = id;
+ }
+
+ public void setAnimationId(int id) {
+ mAnimationId = id;
+ }
+
+ public Component(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height) {
+ this.mComponentId = componentId;
+ this.mX = x;
+ this.mY = y;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mParent = parent;
+ this.mAnimationId = animationId;
+ }
+
+ public Component(int componentId, float x, float y, float width, float height,
+ Component parent) {
+ this(parent, componentId, -1, x, y, width, height);
+ }
+
+ public Component(Component component) {
+ this(component.mParent, component.mComponentId, component.mAnimationId,
+ component.mX, component.mY, component.mWidth, component.mHeight
+ );
+ mList.addAll(component.mList);
+ finalizeCreation();
+ }
+
+ public void finalizeCreation() {
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ ((Component) op).mParent = this;
+ }
+ if (op instanceof AnimationSpec) {
+ mAnimationSpec = (AnimationSpec) op;
+ mAnimationId = mAnimationSpec.getAnimationId();
+ }
+ }
+ }
+
+ @Override
+ public boolean needsMeasure() {
+ return mNeedsMeasure;
+ }
+
+ public void setParent(Component parent) {
+ mParent = parent;
+ }
+
+ public enum Visibility {
+ VISIBLE,
+ INVISIBLE,
+ GONE
+ }
+
+ public boolean isVisible() {
+ if (mVisibility != Visibility.VISIBLE || mParent == null) {
+ return mVisibility == Visibility.VISIBLE;
+ }
+ if (mParent != null) {
+ return mParent.isVisible();
+ }
+ return true;
+ }
+
+ @Override
+ public void measure(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ ComponentMeasure m = measure.get(this);
+ m.setW(mWidth);
+ m.setH(mHeight);
+ }
+
+ @Override
+ public void layout(RemoteContext context, MeasurePass measure) {
+ ComponentMeasure m = measure.get(this);
+ if (!mFirstLayout && context.isAnimationEnabled()) {
+ if (mAnimateMeasure == null) {
+ ComponentMeasure origin = new ComponentMeasure(mComponentId,
+ mX, mY, mWidth, mHeight, mVisibility);
+ ComponentMeasure target = new ComponentMeasure(mComponentId,
+ m.getX(), m.getY(), m.getW(), m.getH(), m.getVisibility());
+ mAnimateMeasure = new AnimateMeasure(context.currentTime, this,
+ origin, target,
+ mAnimationSpec.getMotionDuration(), mAnimationSpec.getVisibilityDuration(),
+ mAnimationSpec.getEnterAnimation(), mAnimationSpec.getExitAnimation(),
+ mAnimationSpec.getMotionEasingType(),
+ mAnimationSpec.getVisibilityEasingType());
+ } else {
+ mAnimateMeasure.updateTarget(m, context.currentTime);
+ }
+ } else {
+ mVisibility = m.getVisibility();
+ }
+ mWidth = m.getW();
+ mHeight = m.getH();
+ setLayoutPosition(m.getX(), m.getY());
+ mFirstLayout = false;
+ }
+
+ public float[] locationInWindow = new float[2];
+
+ public boolean contains(float x, float y) {
+ locationInWindow[0] = 0f;
+ locationInWindow[1] = 0f;
+ getLocationInWindow(locationInWindow);
+ float lx1 = locationInWindow[0];
+ float lx2 = lx1 + mWidth;
+ float ly1 = locationInWindow[1];
+ float ly2 = ly1 + mHeight;
+ return x >= lx1 && x < lx2 && y >= ly1 && y < ly2;
+ }
+
+ public void onClick(float x, float y) {
+ if (!contains(x, y)) {
+ return;
+ }
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ ((Component) op).onClick(x, y);
+ }
+ if (op instanceof ComponentModifiers) {
+ ((ComponentModifiers) op).onClick(x, y);
+ }
+ }
+ }
+
+ public void getLocationInWindow(float[] value) {
+ value[0] += mX;
+ value[1] += mY;
+ if (mParent != null && mParent instanceof Component) {
+ if (mParent instanceof LayoutComponent) {
+ value[0] += ((LayoutComponent) mParent).getMarginLeft();
+ value[1] += ((LayoutComponent) mParent).getMarginTop();
+ }
+ mParent.getLocationInWindow(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "COMPONENT(<" + mComponentId + "> " + getClass().getSimpleName()
+ + ") [" + mX + "," + mY + " - " + mWidth + " x " + mHeight + "] " + textContent()
+ + " Visibility (" + mVisibility + ") ";
+ }
+
+ protected String getSerializedName() {
+ return "COMPONENT";
+ }
+
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, getSerializedName() + " [" + mComponentId
+ + ":" + mAnimationId + "] = "
+ + "[" + mX + ", " + mY + ", " + mWidth + ", " + mHeight + "] "
+ + mVisibility
+ // + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
+ );
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ // nothing
+ }
+
+ /**
+ * Returns the top-level RootLayoutComponent
+ */
+ public RootLayoutComponent getRoot() throws Exception {
+ if (this instanceof RootLayoutComponent) {
+ return (RootLayoutComponent) this;
+ }
+ Component p = mParent;
+ while (!(p instanceof RootLayoutComponent)) {
+ if (p == null) {
+ throw new Exception("No RootLayoutComponent found");
+ }
+ p = p.mParent;
+ }
+ return (RootLayoutComponent) p;
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(indent);
+ builder.append(toString());
+ builder.append("\n");
+ String indent2 = " " + indent;
+ for (Operation op : mList) {
+ builder.append(op.deepToString(indent2));
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Mark itself as needing to be remeasured, and walk back up the tree
+ * to mark each parents as well.
+ */
+ public void invalidateMeasure() {
+ needsRepaint();
+ mNeedsMeasure = true;
+ Component p = mParent;
+ while (p != null) {
+ p.mNeedsMeasure = true;
+ p = p.mParent;
+ }
+ }
+
+ public void needsRepaint() {
+ try {
+ getRoot().mNeedsRepaint = true;
+ } catch (Exception e) {
+ // nothing
+ }
+ }
+
+ public String content() {
+ StringBuilder builder = new StringBuilder();
+ for (Operation op : mList) {
+ builder.append("- ");
+ builder.append(op);
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ public String textContent() {
+ StringBuilder builder = new StringBuilder();
+ for (Operation op : mList) {
+ String letter = "";
+ // if (op instanceof DrawTextRun) {
+ // letter = "[" + ((DrawTextRun) op).text + "]";
+ // }
+ builder.append(letter);
+ }
+ return builder.toString();
+ }
+
+ public void debugBox(Component component, PaintContext context) {
+ float width = component.mWidth;
+ float height = component.mHeight;
+
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(0, 0, 255, 255); // Blue color
+ context.applyPaint(mPaint);
+ context.drawLine(0f, 0f, width, 0f);
+ context.drawLine(width, 0f, width, height);
+ context.drawLine(width, height, 0f, height);
+ context.drawLine(0f, height, 0f, 0f);
+ // context.setColor(255, 0, 0, 255)
+ // context.drawLine(0f, 0f, width, height)
+ // context.drawLine(0f, height, width, 0f)
+ context.restorePaint();
+ }
+
+ public void setLayoutPosition(float x, float y) {
+ this.mX = x;
+ this.mY = y;
+ }
+
+ public float getTranslateX() {
+ if (mParent != null) {
+ return mX - mParent.mX;
+ }
+ return 0f;
+ }
+
+ public float getTranslateY() {
+ if (mParent != null) {
+ return mY - mParent.mY;
+ }
+ return 0f;
+ }
+
+ public void paintingComponent(PaintContext context) {
+ if (mPreTranslate != null) {
+ mPreTranslate.paint(context);
+ }
+ context.save();
+ context.translate(mX, mY);
+ if (context.isDebug()) {
+ debugBox(this, context);
+ }
+ for (Operation op : mList) {
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ }
+ }
+ context.restore();
+ }
+
+ public boolean applyAnimationAsNeeded(PaintContext context) {
+ if (context.isAnimationEnabled() && mAnimateMeasure != null) {
+ mAnimateMeasure.apply(context);
+ needsRepaint();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ if (context.isDebug()) {
+ context.save();
+ context.translate(mX, mY);
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(0, 255, 0, 255); // Green
+ context.applyPaint(mPaint);
+ context.drawLine(0f, 0f, mWidth, 0f);
+ context.drawLine(mWidth, 0f, mWidth, mHeight);
+ context.drawLine(mWidth, mHeight, 0f, mHeight);
+ context.drawLine(0f, mHeight, 0f, 0f);
+ mPaint.setColor(255, 0, 0, 255); // Red
+ context.applyPaint(mPaint);
+ context.drawLine(0f, 0f, mWidth, mHeight);
+ context.drawLine(0f, mHeight, mWidth, 0f);
+ context.restorePaint();
+ context.restore();
+ }
+ if (applyAnimationAsNeeded(context)) {
+ return;
+ }
+ if (mVisibility == Visibility.GONE) {
+ return;
+ }
+ paintingComponent(context);
+ }
+
+ public void getComponents(ArrayList<Component> components) {
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ components.add((Component) op);
+ }
+ }
+ }
+
+ public int getComponentCount() {
+ int count = 0;
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ count += 1 + ((Component) op).getComponentCount();
+ }
+ }
+ return count;
+ }
+
+ public int getPaintId() {
+ if (mAnimationId != -1) {
+ return mAnimationId;
+ }
+ return mComponentId;
+ }
+
+ public boolean doesNeedsRepaint() {
+ return mNeedsRepaint;
+ }
+
+ public Component getComponent(int cid) {
+ if (mComponentId == cid || mAnimationId == cid) {
+ return this;
+ }
+ for (Operation c : mList) {
+ if (c instanceof Component) {
+ Component search = ((Component) c).getComponent(cid);
+ if (search != null) {
+ return search;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
new file mode 100644
index 0000000..8a523a2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
@@ -0,0 +1,82 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+
+import java.util.List;
+
+public class ComponentEnd implements Operation {
+
+ public static final ComponentEnd.Companion COMPANION = new ComponentEnd.Companion();
+
+ @Override
+ public void write(WireBuffer buffer) {
+ Companion.apply(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "COMPONENT_END";
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // nothing
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "ComponentEnd";
+ }
+
+ @Override
+ public int id() {
+ return Operations.COMPONENT_END;
+ }
+
+ public static void apply(WireBuffer buffer) {
+ buffer.start(Operations.COMPONENT_END);
+ }
+
+ public static int size() {
+ return 1 + 4 + 4 + 4;
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new ComponentEnd());
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("End tag for components / layouts. This operation marks the end"
+ + "of a component");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
new file mode 100644
index 0000000..5cfad25
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
@@ -0,0 +1,193 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+
+import java.util.List;
+
+public class ComponentStart implements ComponentStartOperation {
+
+ public static final ComponentStart.Companion COMPANION = new ComponentStart.Companion();
+
+ int mType = DEFAULT;
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+ int mComponentId;
+
+ public int getType() {
+ return mType;
+ }
+
+ public float getX() {
+ return mX;
+ }
+
+ public float getY() {
+ return mY;
+ }
+
+ public float getWidth() {
+ return mWidth;
+ }
+
+ public float getHeight() {
+ return mHeight;
+ }
+
+ public int getComponentId() {
+ return mComponentId;
+ }
+
+ public ComponentStart(int type, int componentId, float width, float height) {
+ this.mType = type;
+ this.mComponentId = componentId;
+ this.mX = 0f;
+ this.mY = 0f;
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ Companion.apply(buffer, mType, mComponentId, mWidth, mHeight);
+ }
+
+ @Override
+ public String toString() {
+ return "COMPONENT_START (type " + mType + " " + Companion.typeDescription(mType)
+ + ") - (" + mX + ", " + mY + " - " + mWidth + " x " + mHeight + ")";
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // nothing
+ }
+
+ public static final int UNKNOWN = -1;
+ public static final int DEFAULT = 0;
+ public static final int ROOT_LAYOUT = 1;
+ public static final int LAYOUT = 2;
+ public static final int LAYOUT_CONTENT = 3;
+ public static final int SCROLL_CONTENT = 4;
+ public static final int BUTTON = 5;
+ public static final int CHECKBOX = 6;
+ public static final int TEXT = 7;
+ public static final int CURVED_TEXT = 8;
+ public static final int STATE_HOST = 9;
+ public static final int CUSTOM = 10;
+ public static final int LOTTIE = 11;
+ public static final int IMAGE = 12;
+ public static final int STATE_BOX_CONTENT = 13;
+ public static final int LAYOUT_BOX = 14;
+ public static final int LAYOUT_ROW = 15;
+ public static final int LAYOUT_COLUMN = 16;
+
+ public static class Companion implements DocumentedCompanionOperation {
+
+
+ public static String typeDescription(int type) {
+ switch (type) {
+ case DEFAULT:
+ return "DEFAULT";
+ case ROOT_LAYOUT:
+ return "ROOT_LAYOUT";
+ case LAYOUT:
+ return "LAYOUT";
+ case LAYOUT_CONTENT:
+ return "CONTENT";
+ case SCROLL_CONTENT:
+ return "SCROLL_CONTENT";
+ case BUTTON:
+ return "BUTTON";
+ case CHECKBOX:
+ return "CHECKBOX";
+ case TEXT:
+ return "TEXT";
+ case CURVED_TEXT:
+ return "CURVED_TEXT";
+ case STATE_HOST:
+ return "STATE_HOST";
+ case LOTTIE:
+ return "LOTTIE";
+ case CUSTOM:
+ return "CUSTOM";
+ case IMAGE:
+ return "IMAGE";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public String name() {
+ return "ComponentStart";
+ }
+
+ @Override
+ public int id() {
+ return Operations.COMPONENT_START;
+ }
+
+ public static void apply(WireBuffer buffer, int type, int componentId,
+ float width, float height) {
+ buffer.start(Operations.COMPONENT_START);
+ buffer.writeInt(type);
+ buffer.writeInt(componentId);
+ buffer.writeFloat(width);
+ buffer.writeFloat(height);
+ }
+
+ public static int size() {
+ return 1 + 4 + 4 + 4;
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int type = buffer.readInt();
+ int componentId = buffer.readInt();
+ float width = buffer.readFloat();
+ float height = buffer.readFloat();
+ operations.add(new ComponentStart(type, componentId, width, height));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Basic component encapsulating draw commands."
+ + "This is not resizable.")
+ .field(INT, "TYPE", "Type of components")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(FLOAT, "WIDTH", "width of the component")
+ .field(FLOAT, "HEIGHT", "height of the component");
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
index 1f04a44..67964ef 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.operations.layout;
-package com.android.systemui.activatable
+import com.android.internal.widget.remotecompose.core.Operation;
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+public interface ComponentStartOperation extends Operation {
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
new file mode 100644
index 0000000..941666a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -0,0 +1,27 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * Indicates a lightweight component (without children) that is only laid out and not able to be
+ * measured. Eg borders, background, clips, etc.
+ */
+public interface DecoratorComponent {
+ void layout(RemoteContext context, float width, float height);
+ void onClick(float x, float y);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
new file mode 100644
index 0000000..f198c4a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -0,0 +1,220 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+
+import java.util.ArrayList;
+
+/**
+ * Component with modifiers and children
+ */
+public class LayoutComponent extends Component {
+
+ protected WidthModifierOperation mWidthModifier = null;
+ protected HeightModifierOperation mHeightModifier = null;
+
+ // Margins
+ protected float mMarginLeft = 0f;
+ protected float mMarginRight = 0f;
+ protected float mMarginTop = 0f;
+ protected float mMarginBottom = 0f;
+
+ protected float mPaddingLeft = 0f;
+ protected float mPaddingRight = 0f;
+ protected float mPaddingTop = 0f;
+ protected float mPaddingBottom = 0f;
+
+ protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
+ protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+
+ public LayoutComponent(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ public float getMarginLeft() {
+ return mMarginLeft;
+ }
+ public float getMarginRight() {
+ return mMarginRight;
+ }
+ public float getMarginTop() {
+ return mMarginTop;
+ }
+ public float getMarginBottom() {
+ return mMarginBottom;
+ }
+
+ public WidthModifierOperation getWidthModifier() {
+ return mWidthModifier;
+ }
+ public HeightModifierOperation getHeightModifier() {
+ return mHeightModifier;
+ }
+
+ public void inflate() {
+ for (Operation op : mList) {
+ if (op instanceof LayoutComponentContent) {
+ ((LayoutComponentContent) op).mParent = this;
+ mChildrenComponents.clear();
+ ((LayoutComponentContent) op).getComponents(mChildrenComponents);
+ if (mChildrenComponents.isEmpty()) {
+ mChildrenComponents.add((Component) op);
+ }
+ } else if (op instanceof ModifierOperation) {
+ mComponentModifiers.add((ModifierOperation) op);
+ } else {
+ // nothing
+ }
+ }
+
+ mList.clear();
+ mList.add(mComponentModifiers);
+ for (Component c : mChildrenComponents) {
+ c.mParent = this;
+ mList.add(c);
+ }
+
+ mX = 0f;
+ mY = 0f;
+ mMarginLeft = 0f;
+ mMarginTop = 0f;
+ mMarginRight = 0f;
+ mMarginBottom = 0f;
+ mPaddingLeft = 0f;
+ mPaddingTop = 0f;
+ mPaddingRight = 0f;
+ mPaddingBottom = 0f;
+
+ boolean applyHorizontalMargin = true;
+ boolean applyVerticalMargin = true;
+ for (Operation op : mComponentModifiers.getList()) {
+ if (op instanceof PaddingModifierOperation) {
+ // We are accumulating padding modifiers to compute the margin
+ // until we hit a dimension; the computed padding for the
+ // content simply accumulate all the padding modifiers.
+ float left = ((PaddingModifierOperation) op).getLeft();
+ float right = ((PaddingModifierOperation) op).getRight();
+ float top = ((PaddingModifierOperation) op).getTop();
+ float bottom = ((PaddingModifierOperation) op).getBottom();
+ if (applyHorizontalMargin) {
+ mMarginLeft += left;
+ mMarginRight += right;
+ }
+ if (applyVerticalMargin) {
+ mMarginTop += top;
+ mMarginBottom += bottom;
+ }
+ mPaddingLeft += left;
+ mPaddingTop += top;
+ mPaddingRight += right;
+ mPaddingBottom += bottom;
+ }
+ if (op instanceof WidthModifierOperation && mWidthModifier == null) {
+ mWidthModifier = (WidthModifierOperation) op;
+ applyHorizontalMargin = false;
+ }
+ if (op instanceof HeightModifierOperation && mHeightModifier == null) {
+ mHeightModifier = (HeightModifierOperation) op;
+ applyVerticalMargin = false;
+ }
+ }
+ if (mWidthModifier == null) {
+ mWidthModifier = new WidthModifierOperation(DimensionModifierOperation.Type.WRAP);
+ }
+ if (mHeightModifier == null) {
+ mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
+ }
+ mWidth = computeModifierDefinedWidth();
+ mHeight = computeModifierDefinedHeight();
+ }
+
+ @Override
+ public String toString() {
+ return "UNKNOWN LAYOUT_COMPONENT";
+ }
+
+ @Override
+ public void paintingComponent(PaintContext context) {
+ context.save();
+ context.translate(mX, mY);
+ mComponentModifiers.paint(context);
+ float tx = mPaddingLeft;
+ float ty = mPaddingTop;
+ context.translate(tx, ty);
+ for (Component child : mChildrenComponents) {
+ child.paint(context);
+ }
+ context.translate(-tx, -ty);
+ context.restore();
+ }
+
+ /**
+ * Traverse the modifiers to compute indicated dimension
+ */
+ public float computeModifierDefinedWidth() {
+ float s = 0f;
+ float e = 0f;
+ float w = 0f;
+ for (Operation c : mComponentModifiers.getList()) {
+ if (c instanceof WidthModifierOperation) {
+ WidthModifierOperation o = (WidthModifierOperation) c;
+ if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+ w = o.getValue();
+ }
+ break;
+ }
+ if (c instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) c;
+ s += pop.getLeft();
+ e += pop.getRight();
+ }
+ }
+ return s + w + e;
+ }
+
+ /**
+ * Traverse the modifiers to compute indicated dimension
+ */
+ public float computeModifierDefinedHeight() {
+ float t = 0f;
+ float b = 0f;
+ float h = 0f;
+ for (Operation c : mComponentModifiers.getList()) {
+ if (c instanceof HeightModifierOperation) {
+ HeightModifierOperation o = (HeightModifierOperation) c;
+ if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+ h = o.getValue();
+ }
+ break;
+ }
+ if (c instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) c;
+ t += pop.getTop();
+ b += pop.getBottom();
+ }
+ }
+ return t + h + b;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
new file mode 100644
index 0000000..769ff6a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
@@ -0,0 +1,68 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+
+import java.util.List;
+
+/**
+ * Represents the content of a LayoutComponent (i.e. the children components)
+ */
+public class LayoutComponentContent extends Component implements ComponentStartOperation {
+
+ public static final LayoutComponentContent.Companion COMPANION =
+ new LayoutComponentContent.Companion();
+
+ public LayoutComponentContent(int componentId, float x, float y,
+ float width, float height, Component parent, int animationId) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "LayoutContent";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_CONTENT;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.LAYOUT_CONTENT);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new LayoutComponentContent(
+ -1, 0, 0, 0, 0, null, -1));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Container for components. BoxLayout, RowLayout and ColumnLayout "
+ + "expects a LayoutComponentContent as a child, encapsulating the "
+ + "components that needs to be laid out.");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
new file mode 100644
index 0000000..dc13768
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -0,0 +1,173 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Represents the root layout component. Entry point to the component tree layout/paint.
+ */
+public class RootLayoutComponent extends Component implements ComponentStartOperation {
+
+ public static final RootLayoutComponent.Companion COMPANION =
+ new RootLayoutComponent.Companion();
+
+ int mCurrentId = -1;
+
+ public RootLayoutComponent(int componentId, float x, float y,
+ float width, float height, Component parent, int animationId) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ public RootLayoutComponent(int componentId, float x, float y,
+ float width, float height, Component parent) {
+ super(parent, componentId, -1, x, y, width, height);
+ }
+
+ @Override
+ public String toString() {
+ return "ROOT (" + mX + ", " + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "ROOT [" + mComponentId + ":" + mAnimationId
+ + "] = [" + mX + ", " + mY + ", " + mWidth + ", " + mHeight + "] " + mVisibility);
+ }
+
+ public int getNextId() {
+ mCurrentId--;
+ return mCurrentId;
+ }
+
+ public void assignIds() {
+ assignId(this);
+ }
+
+ void assignId(Component component) {
+ if (component.mComponentId == -1) {
+ component.mComponentId = getNextId();
+ }
+ for (Operation op : component.mList) {
+ if (op instanceof Component) {
+ assignId((Component) op);
+ }
+ }
+ }
+
+ /**
+ * This will measure then layout the tree of components
+ */
+ public void layout(RemoteContext context) {
+ if (!mNeedsMeasure) {
+ return;
+ }
+ context.lastComponent = this;
+ mWidth = context.mWidth;
+ mHeight = context.mHeight;
+
+ // TODO: reuse MeasurePass
+ MeasurePass measurePass = new MeasurePass();
+ for (Operation op : mList) {
+ if (op instanceof Measurable) {
+ Measurable m = (Measurable) op;
+ m.measure(context.getPaintContext(),
+ 0f, mWidth, 0f, mHeight, measurePass);
+ m.layout(context, measurePass);
+ }
+ }
+ mNeedsMeasure = false;
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ mNeedsRepaint = false;
+ context.getContext().lastComponent = this;
+ context.save();
+
+ if (mParent == null) { // root layout
+ context.clipRect(0f, 0f, mWidth, mHeight);
+ }
+
+ for (Operation op : mList) {
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ }
+ }
+
+ context.restore();
+ }
+
+ public String displayHierarchy() {
+ StringSerializer serializer = new StringSerializer();
+ displayHierarchy(this, 0, serializer);
+ return serializer.toString();
+ }
+
+ public void displayHierarchy(Component component, int indent, StringSerializer serializer) {
+ component.serializeToString(indent, serializer);
+ for (Operation c : component.mList) {
+ if (c instanceof ComponentModifiers) {
+ ((ComponentModifiers) c).serializeToString(indent + 1, serializer);
+ }
+ if (c instanceof Component) {
+ displayHierarchy((Component) c, indent + 1, serializer);
+ }
+ }
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "RootLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_ROOT;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.LAYOUT_ROOT);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new RootLayoutComponent(
+ -1, 0, 0, 0, 0, null, -1));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Root element for a document. Other components / layout managers "
+ + "are children in the component tree starting from this Root component.");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
new file mode 100644
index 0000000..7c6bef4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -0,0 +1,311 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.animation;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+
+/**
+ * Basic interpolation manager between two ComponentMeasures
+ *
+ * Handles position, size and visibility
+ */
+public class AnimateMeasure {
+ long mStartTime = System.currentTimeMillis();
+ Component mComponent;
+ ComponentMeasure mOriginal;
+ ComponentMeasure mTarget;
+ int mDuration;
+ int mDurationVisibilityChange = mDuration;
+ AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN;
+ AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT;
+ int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+ int mVisibilityEasingType = GeneralEasing.CUBIC_ACCELERATE;
+
+ float mP = 0f;
+ float mVp = 0f;
+ FloatAnimation mMotionEasing = new FloatAnimation(mMotionEasingType,
+ mDuration / 1000f, null, 0f, Float.NaN);
+ FloatAnimation mVisibilityEasing = new FloatAnimation(mVisibilityEasingType,
+ mDurationVisibilityChange / 1000f,
+ null, 0f, Float.NaN);
+ ParticleAnimation mParticleAnimation;
+
+ public AnimateMeasure(long startTime, Component component, ComponentMeasure original,
+ ComponentMeasure target, int duration, int durationVisibilityChange,
+ AnimationSpec.ANIMATION enterAnimation,
+ AnimationSpec.ANIMATION exitAnimation,
+ int motionEasingType, int visibilityEasingType) {
+ this.mStartTime = startTime;
+ this.mComponent = component;
+ this.mOriginal = original;
+ this.mTarget = target;
+ this.mDuration = duration;
+ this.mDurationVisibilityChange = durationVisibilityChange;
+ this.mEnterAnimation = enterAnimation;
+ this.mExitAnimation = exitAnimation;
+
+ mMotionEasing.setTargetValue(1f);
+ mVisibilityEasing.setTargetValue(1f);
+ component.mVisibility = target.getVisibility();
+ }
+
+ public void update(long currentTime) {
+ long elapsed = currentTime - mStartTime;
+ mP = Math.min(elapsed / (float) mDuration, 1f);
+ //mP = motionEasing.get(mP);
+ mVp = Math.min(elapsed / (float) mDurationVisibilityChange, 1f);
+ mVp = mVisibilityEasing.get(mVp);
+ }
+
+ public PaintBundle paint = new PaintBundle();
+
+ public void apply(PaintContext context) {
+ update(context.getContext().currentTime);
+
+ mComponent.setX(getX());
+ mComponent.setY(getY());
+ mComponent.setWidth(getWidth());
+ mComponent.setHeight(getHeight());
+
+ float w = mComponent.getWidth();
+ float h = mComponent.getHeight();
+ for (Operation op : mComponent.mList) {
+ if (op instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) op;
+ w -= pop.getLeft() + pop.getRight();
+ h -= pop.getTop() + pop.getBottom();
+ }
+ if (op instanceof DecoratorComponent) {
+ ((DecoratorComponent) op).layout(context.getContext(), w, h);
+ }
+ }
+
+ mComponent.mVisibility = mTarget.getVisibility();
+ if (mOriginal.getVisibility() != mTarget.getVisibility()) {
+ if (mTarget.getVisibility() == Component.Visibility.GONE) {
+ switch (mExitAnimation) {
+ case PARTICLE:
+ // particleAnimation(context, component, original, target, vp)
+ if (mParticleAnimation == null) {
+ mParticleAnimation = new ParticleAnimation();
+ }
+ mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
+ break;
+ case FADE_OUT:
+ context.save();
+ context.savePaint();
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, 1f - mVp);
+ context.applyPaint(paint);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case SLIDE_LEFT:
+ context.save();
+ context.translate(-mVp * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_RIGHT:
+ context.save();
+ context.savePaint();
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, 1f);
+ context.applyPaint(paint);
+ context.translate(mVp * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case SLIDE_TOP:
+ context.save();
+ context.translate(0f,
+ -mVp * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_BOTTOM:
+ context.save();
+ context.translate(0f,
+ mVp * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ default:
+ // particleAnimation(context, component, original, target, vp)
+ if (mParticleAnimation == null) {
+ mParticleAnimation = new ParticleAnimation();
+ }
+ mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
+ break;
+ }
+ } else if (mOriginal.getVisibility() == Component.Visibility.GONE
+ && mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ switch (mEnterAnimation) {
+ case ROTATE:
+ float px = mTarget.getX() + mTarget.getW() / 2f;
+ float py = mTarget.getY() + mTarget.getH() / 2f;
+
+ context.save();
+ context.savePaint();
+ context.matrixRotate(mVp * 360f, px, py);
+ context.matrixScale(1f * mVp, 1f * mVp, px, py);
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, mVp);
+ context.applyPaint(paint);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case FADE_IN:
+ context.save();
+ context.savePaint();
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, mVp);
+ context.applyPaint(paint);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case SLIDE_LEFT:
+ context.save();
+ context.translate(
+ (1f - mVp) * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_RIGHT:
+ context.save();
+ context.translate(
+ -(1f - mVp) * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_TOP:
+ context.save();
+ context.translate(0f,
+ (1f - mVp) * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_BOTTOM:
+ context.save();
+ context.translate(0f,
+ -(1f - mVp) * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ default:
+ break;
+ }
+ } else {
+ mComponent.paintingComponent(context);
+ }
+ } else {
+ mComponent.paintingComponent(context);
+ }
+
+ if (mP >= 1f && mVp >= 1f) {
+ mComponent.mAnimateMeasure = null;
+ mComponent.mVisibility = mTarget.getVisibility();
+ }
+ }
+
+ public boolean isDone() {
+ return mP >= 1f && mVp >= 1f;
+ }
+
+ public float getX() {
+ return mOriginal.getX() * (1 - mP) + mTarget.getX() * mP;
+ }
+
+ public float getY() {
+ return mOriginal.getY() * (1 - mP) + mTarget.getY() * mP;
+ }
+
+ public float getWidth() {
+ return mOriginal.getW() * (1 - mP) + mTarget.getW() * mP;
+ }
+
+ public float getHeight() {
+ return mOriginal.getH() * (1 - mP) + mTarget.getH() * mP;
+ }
+
+ public float getVisibility() {
+ if (mOriginal.getVisibility() == mTarget.getVisibility()) {
+ return 1f;
+ } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ return mVp;
+ } else {
+ return 1 - mVp;
+ }
+ }
+
+ public void updateTarget(ComponentMeasure measure, long currentTime) {
+ mOriginal.setX(getX());
+ mOriginal.setY(getY());
+ mOriginal.setW(getWidth());
+ mOriginal.setH(getHeight());
+ mTarget.setX(measure.getX());
+ mTarget.setY(measure.getY());
+ mTarget.setW(measure.getW());
+ mTarget.setH(measure.getH());
+ mTarget.setVisibility(measure.getVisibility());
+ mStartTime = currentTime;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
new file mode 100644
index 0000000..386d365
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -0,0 +1,186 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.animation;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+
+import java.util.List;
+
+/**
+ * Basic component animation spec
+ */
+public class AnimationSpec implements Operation {
+
+ public static final AnimationSpec.Companion COMPANION = new AnimationSpec.Companion();
+
+ int mAnimationId = -1;
+ int mMotionDuration = 300;
+ int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+ int mVisibilityDuration = 300;
+ int mVisibilityEasingType = GeneralEasing.CUBIC_STANDARD;
+ ANIMATION mEnterAnimation = ANIMATION.FADE_IN;
+ ANIMATION mExitAnimation = ANIMATION.FADE_OUT;
+
+ public AnimationSpec(int animationId, int motionDuration, int motionEasingType,
+ int visibilityDuration, int visibilityEasingType,
+ ANIMATION enterAnimation, ANIMATION exitAnimation) {
+ this.mAnimationId = animationId;
+ this.mMotionDuration = motionDuration;
+ this.mMotionEasingType = motionEasingType;
+ this.mVisibilityDuration = visibilityDuration;
+ this.mVisibilityEasingType = visibilityEasingType;
+ this.mEnterAnimation = enterAnimation;
+ this.mExitAnimation = exitAnimation;
+ }
+
+ public AnimationSpec() {
+ this(-1, 300, GeneralEasing.CUBIC_STANDARD,
+ 300, GeneralEasing.CUBIC_STANDARD,
+ ANIMATION.FADE_IN, ANIMATION.FADE_OUT);
+ }
+
+ public int getAnimationId() {
+ return mAnimationId;
+ }
+
+ public int getMotionDuration() {
+ return mMotionDuration;
+ }
+
+ public int getMotionEasingType() {
+ return mMotionEasingType;
+ }
+
+ public int getVisibilityDuration() {
+ return mVisibilityDuration;
+ }
+
+ public int getVisibilityEasingType() {
+ return mVisibilityEasingType;
+ }
+
+ public ANIMATION getEnterAnimation() {
+ return mEnterAnimation;
+ }
+
+ public ANIMATION getExitAnimation() {
+ return mExitAnimation;
+ }
+
+ @Override
+ public String toString() {
+ return "ANIMATION_SPEC (" + mMotionDuration + " ms)";
+ }
+
+ public enum ANIMATION {
+ FADE_IN,
+ FADE_OUT,
+ SLIDE_LEFT,
+ SLIDE_RIGHT,
+ SLIDE_TOP,
+ SLIDE_BOTTOM,
+ ROTATE,
+ PARTICLE
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ Companion.apply(buffer, mAnimationId, mMotionDuration, mMotionEasingType,
+ mVisibilityDuration, mVisibilityEasingType, mEnterAnimation, mExitAnimation);
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // nothing here
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ @Override
+ public String name() {
+ return "AnimationSpec";
+ }
+
+ @Override
+ public int id() {
+ return Operations.ANIMATION_SPEC;
+ }
+
+ public static int animationToInt(ANIMATION animation) {
+ return animation.ordinal();
+ }
+
+ public static ANIMATION intToAnimation(int value) {
+ switch (value) {
+ case 0:
+ return ANIMATION.FADE_IN;
+ case 1:
+ return ANIMATION.FADE_OUT;
+ case 2:
+ return ANIMATION.SLIDE_LEFT;
+ case 3:
+ return ANIMATION.SLIDE_RIGHT;
+ case 4:
+ return ANIMATION.SLIDE_TOP;
+ case 5:
+ return ANIMATION.SLIDE_BOTTOM;
+ case 6:
+ return ANIMATION.ROTATE;
+ case 7:
+ return ANIMATION.PARTICLE;
+ default:
+ return ANIMATION.FADE_IN;
+ }
+ }
+
+ public static void apply(WireBuffer buffer, int animationId, int motionDuration,
+ int motionEasingType, int visibilityDuration,
+ int visibilityEasingType, ANIMATION enterAnimation,
+ ANIMATION exitAnimation) {
+ buffer.start(Operations.ANIMATION_SPEC);
+ buffer.writeInt(animationId);
+ buffer.writeInt(motionDuration);
+ buffer.writeInt(motionEasingType);
+ buffer.writeInt(visibilityDuration);
+ buffer.writeInt(visibilityEasingType);
+ buffer.writeInt(animationToInt(enterAnimation));
+ buffer.writeInt(animationToInt(exitAnimation));
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int animationId = buffer.readInt();
+ int motionDuration = buffer.readInt();
+ int motionEasingType = buffer.readInt();
+ int visibilityDuration = buffer.readInt();
+ int visibilityEasingType = buffer.readInt();
+ ANIMATION enterAnimation = intToAnimation(buffer.readInt());
+ ANIMATION exitAnimation = intToAnimation(buffer.readInt());
+ AnimationSpec op = new AnimationSpec(animationId, motionDuration, motionEasingType,
+ visibilityDuration, visibilityEasingType, enterAnimation, exitAnimation);
+ operations.add(op);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/Particle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/Particle.java
new file mode 100644
index 0000000..4562692
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/Particle.java
@@ -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.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.animation;
+
+public class Particle {
+ public final float x;
+ public final float y;
+ public float radius;
+ public float r;
+ public float g;
+ public float b;
+
+ public Particle(float x, float y, float radius, float r, float g, float b) {
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
new file mode 100644
index 0000000..5c5d056
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
@@ -0,0 +1,64 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.animation;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ParticleAnimation {
+ HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>();
+
+ PaintBundle mPaint = new PaintBundle();
+ public void animate(PaintContext context, Component component,
+ ComponentMeasure start, ComponentMeasure end,
+ float progress) {
+ ArrayList<Particle> particles = mAllParticles.get(component.getComponentId());
+ if (particles == null) {
+ particles = new ArrayList<Particle>();
+ for (int i = 0; i < 20; i++) {
+ float x = (float) Math.random();
+ float y = (float) Math.random();
+ float radius = (float) Math.random();
+ float r = 250f;
+ float g = 250f;
+ float b = 250f;
+ particles.add(new Particle(x, y, radius, r, g, b));
+ }
+ mAllParticles.put(component.getComponentId(), particles);
+ }
+ context.save();
+ context.savePaint();
+ for (int i = 0; i < particles.size(); i++) {
+ Particle particle = particles.get(i);
+ mPaint.reset();
+ mPaint.setColor(particle.r, particle.g, particle.b,
+ 200 * (1 - progress));
+ context.applyPaint(mPaint);
+ float dx = start.getX() + component.getWidth() * particle.x;
+ float dy = start.getY() + component.getHeight() * particle.y
+ + progress * 0.01f * component.getHeight();
+ float dr = (component.getHeight() + 60) * 0.15f * particle.radius + (30 * progress);
+ context.drawCircle(dx, dy, dr);
+ }
+ context.restorePaint();
+ context.restore();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
new file mode 100644
index 0000000..fea8dd2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -0,0 +1,189 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+import java.util.List;
+
+/**
+ * Simple Box layout implementation
+ */
+public class BoxLayout extends LayoutManager implements ComponentStartOperation {
+
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+
+ public static final BoxLayout.Companion COMPANION = new BoxLayout.Companion();
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+
+ public BoxLayout(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height,
+ int horizontalPositioning, int verticalPositioning) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ }
+
+ public BoxLayout(Component parent, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning) {
+ this(parent, componentId, animationId, 0, 0, 0, 0,
+ horizontalPositioning, verticalPositioning);
+ }
+
+ @Override
+ public String toString() {
+ return "BOX [" + mComponentId + ":" + mAnimationId + "] (" + mX + ", "
+ + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ protected String getSerializedName() {
+ return "BOX";
+ }
+
+ @Override
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ for (Component c : mChildrenComponents) {
+ c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(Math.max(size.getHeight(), m.getH()));
+ }
+ // add padding
+ size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth()));
+ size.setHeight(Math.max(size.getHeight(), computeModifierDefinedHeight()));
+ }
+
+ @Override
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ for (Component child : mChildrenComponents) {
+ child.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
+ }
+ }
+
+ @Override
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure m = measure.get(child);
+ float tx = 0f;
+ float ty = 0f;
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - m.getH()) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - m.getH();
+ break;
+ }
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case CENTER:
+ tx = (selfWidth - m.getW()) / 2f;
+ break;
+ case END:
+ tx = selfWidth - m.getW();
+ break;
+ }
+ m.setX(tx);
+ m.setY(ty);
+ m.setVisibility(child.mVisibility);
+ }
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "BoxLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_BOX;
+ }
+
+ public void apply(WireBuffer buffer, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning) {
+ buffer.start(Operations.LAYOUT_BOX);
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ operations.add(new BoxLayout(null, componentId, animationId,
+ horizontalPositioning, verticalPositioning));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Box layout implementation.\n\n"
+ + "Child components are laid out independently from one another,\n"
+ + " and painted in their hierarchy order (first children drawn"
+ + "before the latter). Horizontal and Vertical positioning"
+ + "are supported.")
+ .examplesDimension(150, 100)
+ .exampleImage("Top", "layout-BoxLayout-start-top.png")
+ .exampleImage("Center", "layout-BoxLayout-center-center.png")
+ .exampleImage("Bottom", "layout-BoxLayout-end-bottom.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(INT, "ANIMATION_ID", "id used to match components,"
+ + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", BoxLayout.START)
+ .possibleValues("CENTER", BoxLayout.CENTER)
+ .possibleValues("END", BoxLayout.END)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", BoxLayout.TOP)
+ .possibleValues("CENTER", BoxLayout.CENTER)
+ .possibleValues("BOTTOM", BoxLayout.BOTTOM);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
new file mode 100644
index 0000000..a1a2de5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -0,0 +1,291 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
+
+import java.util.List;
+
+/**
+ * Simple Column layout implementation
+ * - also supports weight and horizontal/vertical positioning
+ */
+public class ColumnLayout extends LayoutManager implements ComponentStartOperation {
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+ public static final int SPACE_BETWEEN = 6;
+ public static final int SPACE_EVENLY = 7;
+ public static final int SPACE_AROUND = 8;
+
+ public static final ColumnLayout.Companion COMPANION = new ColumnLayout.Companion();
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+ float mSpacedBy = 0f;
+
+ public ColumnLayout(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ mSpacedBy = spacedBy;
+ }
+
+ public ColumnLayout(Component parent, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ this(parent, componentId, animationId, 0, 0, 0, 0,
+ horizontalPositioning, verticalPositioning, spacedBy);
+ }
+
+ @Override
+ public String toString() {
+ return "COLUMN [" + mComponentId + ":" + mAnimationId + "] (" + mX + ", "
+ + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ protected String getSerializedName() {
+ return "COLUMN";
+ }
+
+ @Override
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
+ for (Component c : mChildrenComponents) {
+ c.measure(context, 0f, maxWidth,
+ 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(size.getHeight() + m.getH());
+ }
+ if (!mChildrenComponents.isEmpty()) {
+ size.setHeight(size.getHeight()
+ + (mSpacedBy * (mChildrenComponents.size() - 1)));
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
+ float mh = maxHeight;
+ for (Component child : mChildrenComponents) {
+ child.measure(context, minWidth, maxWidth, minHeight, mh, measure);
+ ComponentMeasure m = measure.get(child);
+ mh -= m.getH();
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ DebugLog.s(() -> "INTERNAL LAYOUT " + this + " (" + mComponentId + ") children: "
+ + mChildrenComponents.size() + " size (" + selfMeasure.getW()
+ + " x " + selfMeasure.getH() + ")");
+ if (mChildrenComponents.isEmpty()) {
+ DebugLog.e();
+ return;
+ }
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+
+ boolean hasWeights = false;
+ float totalWeights = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
+ } else {
+ childrenHeight += childMeasure.getH();
+ }
+ }
+ if (hasWeights) {
+ float availableSpace = selfHeight - childrenHeight;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ float weight = ((LayoutComponent) child).getHeightModifier().getValue();
+ childMeasure.setH((weight * availableSpace) / totalWeights);
+ child.measure(context, childMeasure.getW(),
+ childMeasure.getW(), childMeasure.getH(), childMeasure.getH(), measure);
+ }
+ }
+ }
+
+ childrenHeight = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ childrenHeight += childMeasure.getH();
+ }
+ childrenHeight += mSpacedBy * (mChildrenComponents.size() - 1);
+
+ float tx = 0f;
+ float ty = 0f;
+
+ float verticalGap = 0f;
+ float total = 0f;
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - childrenHeight) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - childrenHeight;
+ break;
+ case SPACE_BETWEEN:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getH();
+ }
+ verticalGap = (selfHeight - total) / (mChildrenComponents.size() - 1);
+ break;
+ case SPACE_EVENLY:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getH();
+ }
+ verticalGap = (selfHeight - total) / (mChildrenComponents.size() + 1);
+ ty = verticalGap;
+ break;
+ case SPACE_AROUND:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getH();
+ }
+ verticalGap = (selfHeight - total) / (mChildrenComponents.size());
+ ty = verticalGap / 2f;
+ break;
+ }
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case CENTER:
+ tx = (selfWidth - childMeasure.getW()) / 2f;
+ break;
+ case END:
+ tx = selfWidth - childMeasure.getW();
+ break;
+ }
+ childMeasure.setX(tx);
+ childMeasure.setY(ty);
+ childMeasure.setVisibility(child.mVisibility);
+ ty += childMeasure.getH();
+ if (mVerticalPositioning == SPACE_BETWEEN
+ || mVerticalPositioning == SPACE_AROUND
+ || mVerticalPositioning == SPACE_EVENLY) {
+ ty += verticalGap;
+ }
+ ty += mSpacedBy;
+ }
+ DebugLog.e();
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "ColumnLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_COLUMN;
+ }
+
+ public void apply(WireBuffer buffer, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ buffer.start(Operations.LAYOUT_COLUMN);
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(new ColumnLayout(null, componentId, animationId,
+ horizontalPositioning, verticalPositioning, spacedBy));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Column layout implementation, positioning components one"
+ + " after the other vertically.\n\n"
+ + "It supports weight and horizontal/vertical positioning.")
+ .examplesDimension(100, 400)
+ .exampleImage("Top", "layout-ColumnLayout-start-top.png")
+ .exampleImage("Center", "layout-ColumnLayout-start-center.png")
+ .exampleImage("Bottom", "layout-ColumnLayout-start-bottom.png")
+ .exampleImage("SpaceEvenly", "layout-ColumnLayout-start-space-evenly.png")
+ .exampleImage("SpaceAround", "layout-ColumnLayout-start-space-around.png")
+ .exampleImage("SpaceBetween", "layout-ColumnLayout-start-space-between.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(INT, "ANIMATION_ID", "id used to match components,"
+ + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", ColumnLayout.START)
+ .possibleValues("CENTER", ColumnLayout.CENTER)
+ .possibleValues("END", ColumnLayout.END)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", ColumnLayout.TOP)
+ .possibleValues("CENTER", ColumnLayout.CENTER)
+ .possibleValues("BOTTOM", ColumnLayout.BOTTOM)
+ .possibleValues("SPACE_BETWEEN", ColumnLayout.SPACE_BETWEEN)
+ .possibleValues("SPACE_EVENLY", ColumnLayout.SPACE_EVENLY)
+ .possibleValues("SPACE_AROUND", ColumnLayout.SPACE_AROUND)
+ .field(FLOAT, "SPACED_BY", "Horizontal spacing between components");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
new file mode 100644
index 0000000..4890683
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -0,0 +1,136 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.managers;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+/**
+ * Base class for layout managers -- resizable components.
+ */
+public abstract class LayoutManager extends LayoutComponent implements Measurable {
+
+ Size mCachedWrapSize = new Size(0f, 0f);
+
+ public LayoutManager(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ /**
+ * Implemented by subclasses to provide a layout/measure pass
+ */
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ // nothing here
+ }
+
+ /**
+ * Subclasses can implement this to provide wrap sizing
+ */
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ // nothing here
+ }
+
+ /**
+ * Subclasses can implement this when not in wrap sizing
+ */
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ // nothing here
+ }
+
+ /**
+ * Base implementation of the measure resolution
+ */
+ @Override
+ public void measure(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ boolean hasWrap = true;
+ float measuredWidth = Math.min(maxWidth,
+ computeModifierDefinedWidth() - mMarginLeft - mMarginRight);
+ float measuredHeight = Math.min(maxHeight,
+ computeModifierDefinedHeight() - mMarginTop - mMarginBottom);
+ float insetMaxWidth = maxWidth - mMarginLeft - mMarginRight;
+ float insetMaxHeight = maxHeight - mMarginTop - mMarginBottom;
+ if (mWidthModifier.isWrap() || mHeightModifier.isWrap()) {
+ mCachedWrapSize.setWidth(0f);
+ mCachedWrapSize.setHeight(0f);
+ computeWrapSize(context, maxWidth, maxHeight, measure, mCachedWrapSize);
+ measuredWidth = mCachedWrapSize.getWidth();
+ measuredHeight = mCachedWrapSize.getHeight();
+ } else {
+ hasWrap = false;
+ }
+ if (mWidthModifier.isFill()) {
+ measuredWidth = insetMaxWidth;
+ } else if (mWidthModifier.hasWeight()) {
+ measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
+ } else {
+ measuredWidth = Math.max(measuredWidth, minWidth);
+ measuredWidth = Math.min(measuredWidth, insetMaxWidth);
+ }
+ if (mHeightModifier.isFill()) {
+ measuredHeight = insetMaxHeight;
+ } else if (mHeightModifier.hasWeight()) {
+ measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
+ } else {
+ measuredHeight = Math.max(measuredHeight, minHeight);
+ measuredHeight = Math.min(measuredHeight, insetMaxHeight);
+ }
+ if (minWidth == maxWidth) {
+ measuredWidth = maxWidth;
+ }
+ if (minHeight == maxHeight) {
+ measuredHeight = maxHeight;
+ }
+ measuredWidth = Math.min(measuredWidth, insetMaxWidth);
+ measuredHeight = Math.min(measuredHeight, insetMaxHeight);
+ if (!hasWrap) {
+ computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+ }
+ measuredWidth += mMarginLeft + mMarginRight;
+ measuredHeight += mMarginTop + mMarginBottom;
+
+ ComponentMeasure m = measure.get(this);
+ m.setW(measuredWidth);
+ m.setH(measuredHeight);
+
+ internalLayoutMeasure(context, measure);
+ }
+
+ /**
+ * basic layout of internal components
+ */
+ @Override
+ public void layout(RemoteContext context, MeasurePass measure) {
+ super.layout(context, measure);
+ ComponentMeasure self = measure.get(this);
+
+ mComponentModifiers.layout(context, self.getW(), self.getH());
+ for (Component c : mChildrenComponents) {
+ c.layout(context, measure);
+ }
+ this.mNeedsMeasure = false;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
new file mode 100644
index 0000000..07e2ea1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -0,0 +1,294 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
+
+import java.util.List;
+
+/**
+ * Simple Row layout implementation
+ * - also supports weight and horizontal/vertical positioning
+ */
+public class RowLayout extends LayoutManager implements ComponentStartOperation {
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+ public static final int SPACE_BETWEEN = 6;
+ public static final int SPACE_EVENLY = 7;
+ public static final int SPACE_AROUND = 8;
+
+ public static final RowLayout.Companion COMPANION = new RowLayout.Companion();
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+ float mSpacedBy = 0f;
+
+ public RowLayout(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ mSpacedBy = spacedBy;
+ }
+
+ public RowLayout(Component parent, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ this(parent, componentId, animationId, 0, 0, 0, 0,
+ horizontalPositioning, verticalPositioning, spacedBy);
+ }
+ @Override
+ public String toString() {
+ return "ROW [" + mComponentId + ":" + mAnimationId + "] (" + mX + ", "
+ + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ protected String getSerializedName() {
+ return "ROW";
+ }
+
+ @Override
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
+ for (Component c : mChildrenComponents) {
+ c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ size.setWidth(size.getWidth() + m.getW());
+ size.setHeight(Math.max(size.getHeight(), m.getH()));
+ }
+ if (!mChildrenComponents.isEmpty()) {
+ size.setWidth(size.getWidth()
+ + (mSpacedBy * (mChildrenComponents.size() - 1)));
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
+ float mw = maxWidth;
+ for (Component child : mChildrenComponents) {
+ child.measure(context, minWidth, mw, minHeight, maxHeight, measure);
+ ComponentMeasure m = measure.get(child);
+ mw -= m.getW();
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ DebugLog.s(() -> "INTERNAL LAYOUT " + this + " (" + mComponentId + ") children: "
+ + mChildrenComponents.size() + " size (" + selfMeasure.getW()
+ + " x " + selfMeasure.getH() + ")");
+ if (mChildrenComponents.isEmpty()) {
+ DebugLog.e();
+ return;
+ }
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+
+ boolean hasWeights = false;
+ float totalWeights = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
+ } else {
+ childrenWidth += childMeasure.getW();
+ }
+ }
+
+ // TODO: need to move the weight measuring in the measure function,
+ // currently we'll measure unnecessarily
+ if (hasWeights) {
+ float availableSpace = selfWidth - childrenWidth;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ float weight = ((LayoutComponent) child).getWidthModifier().getValue();
+ childMeasure.setW((weight * availableSpace) / totalWeights);
+ child.measure(context, childMeasure.getW(),
+ childMeasure.getW(), childMeasure.getH(), childMeasure.getH(), measure);
+ }
+ }
+ }
+
+ childrenWidth = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ childrenWidth += childMeasure.getW();
+ childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+ }
+ childrenWidth += mSpacedBy * (mChildrenComponents.size() - 1);
+
+ float tx = 0f;
+ float ty = 0f;
+
+ float horizontalGap = 0f;
+ float total = 0f;
+
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case END:
+ tx = selfWidth - childrenWidth;
+ break;
+ case CENTER:
+ tx = (selfWidth - childrenWidth) / 2f;
+ break;
+ case SPACE_BETWEEN:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getW();
+ }
+ horizontalGap = (selfWidth - total) / (mChildrenComponents.size() - 1);
+ break;
+ case SPACE_EVENLY:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getW();
+ }
+ horizontalGap = (selfWidth - total) / (mChildrenComponents.size() + 1);
+ tx = horizontalGap;
+ break;
+ case SPACE_AROUND:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getW();
+ }
+ horizontalGap = (selfWidth - total) / (mChildrenComponents.size());
+ tx = horizontalGap / 2f;
+ break;
+ }
+
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - childMeasure.getH()) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - childMeasure.getH();
+ break;
+ }
+ childMeasure.setX(tx);
+ childMeasure.setY(ty);
+ childMeasure.setVisibility(child.mVisibility);
+ tx += childMeasure.getW();
+ if (mHorizontalPositioning == SPACE_BETWEEN
+ || mHorizontalPositioning == SPACE_AROUND
+ || mHorizontalPositioning == SPACE_EVENLY) {
+ tx += horizontalGap;
+ }
+ tx += mSpacedBy;
+ }
+ DebugLog.e();
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "RowLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_ROW;
+ }
+
+ public void apply(WireBuffer buffer, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ buffer.start(Operations.LAYOUT_ROW);
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(new RowLayout(null, componentId, animationId,
+ horizontalPositioning, verticalPositioning, spacedBy));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Row layout implementation, positioning components one"
+ + " after the other horizontally.\n\n"
+ + "It supports weight and horizontal/vertical positioning.")
+ .examplesDimension(400, 100)
+ .exampleImage("Start", "layout-RowLayout-start-top.png")
+ .exampleImage("Center", "layout-RowLayout-center-top.png")
+ .exampleImage("End", "layout-RowLayout-end-top.png")
+ .exampleImage("SpaceEvenly", "layout-RowLayout-space-evenly-top.png")
+ .exampleImage("SpaceAround", "layout-RowLayout-space-around-top.png")
+ .exampleImage("SpaceBetween", "layout-RowLayout-space-between-top.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(INT, "ANIMATION_ID", "id used to match components,"
+ + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", RowLayout.START)
+ .possibleValues("CENTER", RowLayout.CENTER)
+ .possibleValues("END", RowLayout.END)
+ .possibleValues("SPACE_BETWEEN", RowLayout.SPACE_BETWEEN)
+ .possibleValues("SPACE_EVENLY", RowLayout.SPACE_EVENLY)
+ .possibleValues("SPACE_AROUND", RowLayout.SPACE_AROUND)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", RowLayout.TOP)
+ .possibleValues("CENTER", RowLayout.CENTER)
+ .possibleValues("BOTTOM", RowLayout.BOTTOM)
+ .field(FLOAT, "SPACED_BY", "Horizontal spacing between components");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
new file mode 100644
index 0000000..8dc10d5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -0,0 +1,91 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.measure;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+
+/**
+ * Encapsulate the result of a measure pass for a component
+ */
+public class ComponentMeasure {
+ int mId = -1;
+ float mX;
+ float mY;
+ float mW;
+ float mH;
+ Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+
+ public void setX(float value) {
+ mX = value;
+ }
+ public void setY(float value) {
+ mY = value;
+ }
+ public void setW(float value) {
+ mW = value;
+ }
+ public void setH(float value) {
+ mH = value;
+ }
+ public float getX() {
+ return mX;
+ }
+ public float getY() {
+ return mY;
+ }
+ public float getW() {
+ return mW;
+ }
+ public float getH() {
+ return mH;
+ }
+
+ public Component.Visibility getVisibility() {
+ return mVisibility;
+ }
+
+ public void setVisibility(Component.Visibility visibility) {
+ mVisibility = visibility;
+ }
+
+ public ComponentMeasure(int id, float x, float y, float w, float h,
+ Component.Visibility visibility) {
+ this.mId = id;
+ this.mX = x;
+ this.mY = y;
+ this.mW = w;
+ this.mH = h;
+ this.mVisibility = visibility;
+ }
+
+ public ComponentMeasure(int id, float x, float y, float w, float h) {
+ this(id, x, y, w, h, Component.Visibility.VISIBLE);
+ }
+
+ public ComponentMeasure(Component component) {
+ this(component.getComponentId(), component.getX(), component.getY(),
+ component.getWidth(), component.getHeight(),
+ component.mVisibility);
+ }
+
+ public void copyFrom(ComponentMeasure m) {
+ mX = m.mX;
+ mY = m.mY;
+ mW = m.mW;
+ mH = m.mH;
+ mVisibility = m.mVisibility;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
new file mode 100644
index 0000000..d167d9b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
@@ -0,0 +1,45 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.measure;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * Interface describing the measure/layout contract for components
+ */
+public interface Measurable {
+
+ /**
+ * Measure a component and store the result of the measure in the provided MeasurePass.
+ * This does not apply the measure to the component.
+ */
+ void measure(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure);
+
+ /**
+ * Apply a given measure to the component
+ */
+ void layout(RemoteContext context, MeasurePass measure);
+
+ /**
+ * Return true if the component needs to be remeasured
+ * @return true if need to remeasured, false otherwise
+ */
+ boolean needsMeasure();
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
new file mode 100644
index 0000000..6801deb
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
@@ -0,0 +1,63 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.measure;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+
+import java.util.HashMap;
+
+/**
+ * Represents the result of a measure pass on the entire hierarchy
+ * TODO: optimize to use a flat array vs the current hashmap
+ */
+public class MeasurePass {
+ HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
+
+ public void clear() {
+ mList.clear();
+ }
+
+ public void add(ComponentMeasure measure) throws Exception {
+ if (measure.mId == -1) {
+ throw new Exception("Component has no id!");
+ }
+ mList.put(measure.mId, measure);
+ }
+
+ public boolean contains(int id) {
+ return mList.containsKey(id);
+ }
+
+ public ComponentMeasure get(Component c) {
+ if (!mList.containsKey(c.getComponentId())) {
+ ComponentMeasure measure = new ComponentMeasure(c.getComponentId(),
+ c.getX(), c.getY(), c.getWidth(), c.getHeight());
+ mList.put(c.getComponentId(), measure);
+ return measure;
+ }
+ return mList.get(c.getComponentId());
+ }
+
+ public ComponentMeasure get(int id) {
+ if (!mList.containsKey(id)) {
+ ComponentMeasure measure = new ComponentMeasure(id,
+ 0f, 0f, 0f, 0f, Component.Visibility.GONE);
+ mList.put(id, measure);
+ return measure;
+ }
+ return mList.get(id);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Size.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Size.java
new file mode 100644
index 0000000..b11d8e8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Size.java
@@ -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.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.measure;
+
+/**
+ * Basic data class representing a component size, used during layout computations.
+ */
+public class Size {
+ float mWidth;
+ float mHeight;
+ public Size(float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ public void setWidth(float value) {
+ mWidth = value;
+ }
+
+ public void setHeight(float value) {
+ mHeight = value;
+ }
+
+ public float getWidth() {
+ return mWidth;
+ }
+
+ public float getHeight() {
+ return mHeight;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
new file mode 100644
index 0000000..6f48aee
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -0,0 +1,146 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Component size-aware background draw
+ */
+public class BackgroundModifierOperation extends DecoratorModifierOperation {
+
+ public static final BackgroundModifierOperation.Companion COMPANION =
+ new BackgroundModifierOperation.Companion();
+
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+ float mR;
+ float mG;
+ float mB;
+ float mA;
+ int mShapeType = ShapeType.RECTANGLE;
+
+ public PaintBundle mPaint = new PaintBundle();
+
+ public BackgroundModifierOperation(float x, float y, float width, float height,
+ float r, float g, float b, float a,
+ int shapeType) {
+ this.mX = x;
+ this.mY = y;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mR = r;
+ this.mG = g;
+ this.mB = b;
+ this.mA = a;
+ this.mShapeType = shapeType;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mX, mY, mWidth, mHeight, mR, mG, mB, mA, mShapeType);
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "BACKGROUND = [" + mX + ", "
+ + mY + ", " + mWidth + ", " + mHeight
+ + "] color [" + mR + ", " + mG + ", " + mB + ", " + mA
+ + "] shape [" + mShapeType + "]");
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public String toString() {
+ return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+
+
+ @Override
+ public String name() {
+ return "OrigamiBackground";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_BACKGROUND;
+ }
+
+ public void apply(WireBuffer buffer, float x, float y, float width, float height,
+ float r, float g, float b, float a, int shapeType) {
+ buffer.start(Operations.MODIFIER_BACKGROUND);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ buffer.writeFloat(width);
+ buffer.writeFloat(height);
+ buffer.writeFloat(r);
+ buffer.writeFloat(g);
+ buffer.writeFloat(b);
+ buffer.writeFloat(a);
+ // shape type
+ buffer.writeInt(shapeType);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ float width = buffer.readFloat();
+ float height = buffer.readFloat();
+ float r = buffer.readFloat();
+ float g = buffer.readFloat();
+ float b = buffer.readFloat();
+ float a = buffer.readFloat();
+ // shape type
+ int shapeType = buffer.readInt();
+ operations.add(new BackgroundModifierOperation(x, y, width, height,
+ r, g, b, a, shapeType));
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(mR, mG, mB, mA);
+ context.applyPaint(mPaint);
+ if (mShapeType == ShapeType.RECTANGLE) {
+ context.drawRect(0f, 0f, mWidth, mHeight);
+ } else if (mShapeType == ShapeType.CIRCLE) {
+ context.drawCircle(mWidth / 2f, mHeight / 2f,
+ Math.min(mWidth, mHeight) / 2f);
+ }
+ context.restorePaint();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
new file mode 100644
index 0000000..0b9c01b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Component size-aware border draw
+ */
+public class BorderModifierOperation extends DecoratorModifierOperation {
+
+ public static final BorderModifierOperation.Companion COMPANION =
+ new BorderModifierOperation.Companion();
+
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+ float mBorderWidth;
+ float mRoundedCorner;
+ float mR;
+ float mG;
+ float mB;
+ float mA;
+ int mShapeType = ShapeType.RECTANGLE;
+
+ public PaintBundle paint = new PaintBundle();
+
+ public BorderModifierOperation(float x, float y, float width, float height,
+ float borderWidth, float roundedCorner,
+ float r, float g, float b, float a, int shapeType) {
+ this.mX = x;
+ this.mY = y;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mBorderWidth = borderWidth;
+ this.mRoundedCorner = roundedCorner;
+ this.mR = r;
+ this.mG = g;
+ this.mB = b;
+ this.mA = a;
+ this.mShapeType = shapeType;
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "BORDER = [" + mX + ", " + mY + ", "
+ + mWidth + ", " + mHeight + "] "
+ + "color [" + mR + ", " + mG + ", " + mB + ", " + mA + "] "
+ + "border [" + mBorderWidth + ", " + mRoundedCorner + "] "
+ + "shape [" + mShapeType + "]");
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mX, mY, mWidth, mHeight, mBorderWidth, mRoundedCorner,
+ mR, mG, mB, mA, mShapeType);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public String toString() {
+ return "BorderModifierOperation(" + mX + "," + mY + " - " + mWidth + " x " + mHeight + ") "
+ + "borderWidth(" + mBorderWidth + ") "
+ + "color(" + mR + "," + mG + "," + mB + "," + mA + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+
+ @Override
+ public String name() {
+ return "BorderModifier";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_BORDER;
+ }
+
+ public void apply(WireBuffer buffer, float x, float y, float width, float height,
+ float borderWidth, float roundedCorner,
+ float r, float g, float b, float a,
+ int shapeType) {
+ buffer.start(Operations.MODIFIER_BORDER);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ buffer.writeFloat(width);
+ buffer.writeFloat(height);
+ buffer.writeFloat(borderWidth);
+ buffer.writeFloat(roundedCorner);
+ buffer.writeFloat(r);
+ buffer.writeFloat(g);
+ buffer.writeFloat(b);
+ buffer.writeFloat(a);
+ // shape type
+ buffer.writeInt(shapeType);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ float width = buffer.readFloat();
+ float height = buffer.readFloat();
+ float bw = buffer.readFloat();
+ float rc = buffer.readFloat();
+ float r = buffer.readFloat();
+ float g = buffer.readFloat();
+ float b = buffer.readFloat();
+ float a = buffer.readFloat();
+ // shape type
+ int shapeType = buffer.readInt();
+ operations.add(new BorderModifierOperation(x, y, width, height, bw,
+ rc, r, g, b, a, shapeType));
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.savePaint();
+ paint.reset();
+ paint.setColor(mR, mG, mB, mA);
+ paint.setStrokeWidth(mBorderWidth);
+ paint.setStyle(PaintBundle.STYLE_STROKE);
+ context.applyPaint(paint);
+ if (mShapeType == ShapeType.RECTANGLE) {
+ context.drawRect(0f, 0f, mWidth, mHeight);
+ } else {
+ float size = mRoundedCorner;
+ if (mShapeType == ShapeType.CIRCLE) {
+ size = Math.min(mWidth, mHeight) / 2f;
+ }
+ context.drawRoundRect(0f, 0f, mWidth, mHeight, size, size);
+ }
+ context.restorePaint();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
new file mode 100644
index 0000000..30357af
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -0,0 +1,88 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Support modifier clip with a rectangle
+ */
+public class ClipRectModifierOperation extends DecoratorModifierOperation {
+
+ public static final ClipRectModifierOperation.Companion COMPANION =
+ new ClipRectModifierOperation.Companion();
+
+ float mWidth;
+ float mHeight;
+
+
+ @Override
+ public void paint(PaintContext context) {
+ context.clipRect(0f, 0f, mWidth, mHeight);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public void onClick(float x, float y) {
+ // nothing
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(
+ indent, "CLIP_RECT = [" + mWidth + ", " + mHeight + "]");
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer);
+ }
+
+ public static class Companion implements CompanionOperation {
+
+ @Override
+ public String name() {
+ return "ClipRectModifier";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_CLIP_RECT;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.MODIFIER_CLIP_RECT);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new ClipRectModifierOperation());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
new file mode 100644
index 0000000..2ef0b9d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -0,0 +1,109 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.ArrayList;
+
+/**
+ * Maintain a list of modifiers
+ */
+public class ComponentModifiers extends PaintOperation implements DecoratorComponent {
+ ArrayList<ModifierOperation> mList = new ArrayList<>();
+
+ public ArrayList<ModifierOperation> getList() {
+ return mList;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ // nothing
+ }
+
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "MODIFIERS");
+ for (ModifierOperation m : mList) {
+ m.serializeToString(indent + 1, serializer);
+ }
+ }
+
+ public void add(ModifierOperation operation) {
+ mList.add(operation);
+ }
+
+ public int size() {
+ return mList.size();
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ float tx = 0f;
+ float ty = 0f;
+ for (ModifierOperation op : mList) {
+ if (op instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) op;
+ context.translate(pop.getLeft(), pop.getTop());
+ tx += pop.getLeft();
+ ty += pop.getTop();
+ }
+ if (op instanceof MatrixSave || op instanceof MatrixRestore) {
+ continue;
+ }
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ }
+ }
+ // Back out the translates created by paddings
+ // TODO: we should be able to get rid of this when drawing the content of a component
+ context.translate(-tx, -ty);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ float w = width;
+ float h = height;
+ for (ModifierOperation op : mList) {
+ if (op instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) op;
+ w -= pop.getLeft() + pop.getRight();
+ h -= pop.getTop() + pop.getBottom();
+ }
+ if (op instanceof DecoratorComponent) {
+ ((DecoratorComponent) op).layout(context, w, h);
+ }
+ }
+ }
+
+ public void addAll(ArrayList<ModifierOperation> operations) {
+ mList.addAll(operations);
+ }
+
+ public void onClick(float x, float y) {
+ for (ModifierOperation op : mList) {
+ if (op instanceof DecoratorComponent) {
+ ((DecoratorComponent) op).onClick(x, y);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
new file mode 100644
index 0000000..bf9b27b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
@@ -0,0 +1,32 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+
+/**
+ * Represents a decorator modifier (lightweight component), ie a modifier
+ * that impacts the visual output (background, border...)
+ */
+public abstract class DecoratorModifierOperation extends PaintOperation
+ implements ModifierOperation, DecoratorComponent {
+
+ @Override
+ public void onClick(float x, float y) {
+ // nothing
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
new file mode 100644
index 0000000..04e9431
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -0,0 +1,159 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Base class for dimension modifiers
+ */
+public class DimensionModifierOperation implements ModifierOperation {
+
+ public static final DimensionModifierOperation.Companion COMPANION =
+ new DimensionModifierOperation.Companion(0, "DIMENSION");
+
+ public enum Type {
+ EXACT, FILL, WRAP, WEIGHT, INTRINSIC_MIN, INTRINSIC_MAX;
+
+ static Type fromInt(int value) {
+ switch (value) {
+ case 0: return EXACT;
+ case 1: return FILL;
+ case 2: return WRAP;
+ case 3: return WEIGHT;
+ case 4: return INTRINSIC_MIN;
+ case 5: return INTRINSIC_MAX;
+ }
+ return EXACT;
+ }
+ }
+
+ Type mType = Type.EXACT;
+ float mValue = Float.NaN;
+
+ public DimensionModifierOperation(Type type, float value) {
+ mType = type;
+ mValue = value;
+ }
+
+ public DimensionModifierOperation(Type type) {
+ this(type, Float.NaN);
+ }
+
+ public DimensionModifierOperation(float value) {
+ this(Type.EXACT, value);
+ }
+
+
+ public boolean hasWeight() {
+ return mType == Type.WEIGHT;
+ }
+
+ public boolean isWrap() {
+ return mType == Type.WRAP;
+ }
+
+ public boolean isFill() {
+ return mType == Type.FILL;
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ public float getValue() {
+ return mValue;
+ }
+
+ public void setValue(float value) {
+ this.mValue = value;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mType.ordinal(), mValue);
+ }
+
+ public String serializedName() {
+ return "DIMENSION";
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ if (mType == Type.EXACT) {
+ serializer.append(indent, serializedName() + " = " + mValue);
+ }
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public String toString() {
+ return "DimensionModifierOperation(" + mValue + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+
+ int mOperation;
+ String mName;
+
+ public Companion(int operation, String name) {
+ mOperation = operation;
+ mName = name;
+ }
+
+ @Override
+ public String name() {
+ return mName;
+ }
+
+ @Override
+ public int id() {
+ return mOperation;
+ }
+
+ public void apply(WireBuffer buffer, int type, float value) {
+ buffer.start(mOperation);
+ buffer.writeInt(type);
+ buffer.writeFloat(value);
+ }
+
+ public Operation construct(Type type, float value) {
+ return null;
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ Type type = Type.fromInt(buffer.readInt());
+ float value = buffer.readFloat();
+ Operation op = construct(type, value);
+ operations.add(op);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
new file mode 100644
index 0000000..81173c3
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.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.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+
+/**
+ * Set the height dimension on a component
+ */
+public class HeightModifierOperation extends DimensionModifierOperation {
+
+ public static final DimensionModifierOperation.Companion COMPANION =
+ new DimensionModifierOperation.Companion(Operations.MODIFIER_HEIGHT, "WIDTH") {
+ @Override
+ public Operation construct(DimensionModifierOperation.Type type, float value) {
+ return new HeightModifierOperation(type, value);
+ }
+ };
+
+ public HeightModifierOperation(Type type, float value) {
+ super(type, value);
+ }
+
+ public HeightModifierOperation(Type type) {
+ super(type);
+ }
+
+ public HeightModifierOperation(float value) {
+ super(value);
+ }
+
+ @Override
+ public String toString() {
+ return "Height(" + mValue + ")";
+ }
+
+ @Override
+ public String serializedName() {
+ return "HEIGHT";
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
copy to core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
index e5aedc0..5299719 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/data/model/OobeSchedulerInfo.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
@@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
-package com.android.systemui.inputdevice.oobe.data.model
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
-data class OobeSchedulerInfo(
- val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(),
- val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo()
-)
-
-data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectionTime: Long? = null) {
- val wasEverConnected: Boolean
- get() = connectionTime != null
+/**
+ * Represents a modifier
+ */
+public interface ModifierOperation extends Operation {
+ void serializeToString(int indent, StringSerializer serializer);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
new file mode 100644
index 0000000..5ea6a97
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -0,0 +1,135 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Represents a padding modifier.
+ * Padding modifiers can be chained and will impact following modifiers.
+ */
+public class PaddingModifierOperation implements ModifierOperation {
+
+ public static final PaddingModifierOperation.Companion COMPANION =
+ new PaddingModifierOperation.Companion();
+
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ public PaddingModifierOperation(float left, float top, float right, float bottom) {
+ this.mLeft = left;
+ this.mTop = top;
+ this.mRight = right;
+ this.mBottom = bottom;
+ }
+
+ public float getLeft() {
+ return mLeft;
+ }
+
+ public float getTop() {
+ return mTop;
+ }
+
+ public float getRight() {
+ return mRight;
+ }
+
+ public float getBottom() {
+ return mBottom;
+ }
+
+ public void setLeft(float left) {
+ this.mLeft = left;
+ }
+
+ public void setTop(float top) {
+ this.mTop = top;
+ }
+
+ public void setRight(float right) {
+ this.mRight = right;
+ }
+
+ public void setBottom(float bottom) {
+ this.mBottom = bottom;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "PADDING = [" + mLeft + ", " + mTop + ", "
+ + mRight + ", " + mBottom + "]");
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public String toString() {
+ return "PaddingModifierOperation(" + mLeft + ", " + mTop
+ + ", " + mRight + ", " + mBottom + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+ @Override
+ public String name() {
+ return "PaddingModifierOperation";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_PADDING;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left, float top, float right, float bottom) {
+ buffer.start(Operations.MODIFIER_PADDING);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float left = buffer.readFloat();
+ float top = buffer.readFloat();
+ float right = buffer.readFloat();
+ float bottom = buffer.readFloat();
+ operations.add(new PaddingModifierOperation(left, top, right, bottom));
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
new file mode 100644
index 0000000..9c57c6a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -0,0 +1,78 @@
+/*
+ * 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.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.DrawBase4;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+/**
+ * Support clip with a rectangle
+ */
+public class RoundedClipRectModifierOperation extends DrawBase4
+ implements ModifierOperation, DecoratorComponent {
+
+ public static final Companion COMPANION =
+ new Companion(Operations.MODIFIER_ROUNDED_CLIP_RECT) {
+ @Override
+ public Operation construct(float x1,
+ float y1,
+ float x2,
+ float y2) {
+ return new RoundedClipRectModifierOperation(x1, y1, x2, y2);
+ }
+ };
+ float mWidth;
+ float mHeight;
+
+
+ public RoundedClipRectModifierOperation(
+ float topStart,
+ float topEnd,
+ float bottomStart,
+ float bottomEnd) {
+ super(topStart, topEnd, bottomStart, bottomEnd);
+ mName = "ModifierRoundedClipRect";
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.roundedClipRect(mWidth, mHeight, mX1, mY1, mX2, mY2);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public void onClick(float x, float y) {
+ // nothing
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(
+ indent, "ROUND_CLIP = [" + mWidth + ", " + mHeight
+ + ", " + mX1 + ", " + mY1
+ + ", " + mX2 + ", " + mY2 + "]");
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ShapeType.java
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ShapeType.java
index 1f04a44..e425b4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ShapeType.java
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
-package com.android.systemui.activatable
-
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+/**
+ * Known shapes, used for modifiers (clip/background/border)
+ */
+public class ShapeType {
+ public static int RECTANGLE = 0;
+ public static int CIRCLE = 1;
+ public static int ROUNDED_RECTANGLE = 2;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
new file mode 100644
index 0000000..c46c8d7
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.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.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+
+/**
+ * Set the width dimension on a component
+ */
+public class WidthModifierOperation extends DimensionModifierOperation {
+
+ public static final DimensionModifierOperation.Companion COMPANION =
+ new DimensionModifierOperation.Companion(Operations.MODIFIER_WIDTH, "WIDTH") {
+ @Override
+ public Operation construct(DimensionModifierOperation.Type type, float value) {
+ return new WidthModifierOperation(type, value);
+ }
+ };
+
+ public WidthModifierOperation(Type type, float value) {
+ super(type, value);
+ }
+
+ public WidthModifierOperation(Type type) {
+ super(type);
+ }
+
+ public WidthModifierOperation(float value) {
+ super(value);
+ }
+
+ @Override
+ public String toString() {
+ return "Width(" + mValue + ")";
+ }
+
+ @Override
+ public String serializedName() {
+ return "WIDTH";
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
new file mode 100644
index 0000000..7ccf7f4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.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.widget.remotecompose.core.operations.layout.utils;
+
+import java.util.ArrayList;
+
+/**
+ * Internal utility debug class
+ */
+public class DebugLog {
+
+ public static final boolean DEBUG_LAYOUT_ON = false;
+
+ public static class Node {
+ public Node parent;
+ public String name;
+ public String endString;
+ public ArrayList<Node> list = new ArrayList<>();
+
+ public Node(Node parent, String name) {
+ this.parent = parent;
+ this.name = name;
+ this.endString = name + " DONE";
+ if (parent != null) {
+ parent.add(this);
+ }
+ }
+
+ public void add(Node node) {
+ list.add(node);
+ }
+ }
+
+ public static class LogNode extends Node {
+ public LogNode(Node parent, String name) {
+ super(parent, name);
+ }
+ }
+
+ public static Node node = new Node(null, "Root");
+ public static Node currentNode = node;
+
+ public static void clear() {
+ node = new Node(null, "Root");
+ currentNode = node;
+ }
+
+ public static void s(StringValueSupplier valueSupplier) {
+ if (DEBUG_LAYOUT_ON) {
+ currentNode = new Node(currentNode, valueSupplier.getString());
+ }
+ }
+
+ public static void log(StringValueSupplier valueSupplier) {
+ if (DEBUG_LAYOUT_ON) {
+ new LogNode(currentNode, valueSupplier.getString());
+ }
+ }
+
+ public static void e() {
+ if (DEBUG_LAYOUT_ON) {
+ if (currentNode.parent != null) {
+ currentNode = currentNode.parent;
+ } else {
+ currentNode = node;
+ }
+ }
+ }
+
+ public static void e(StringValueSupplier valueSupplier) {
+ if (DEBUG_LAYOUT_ON) {
+ currentNode.endString = valueSupplier.getString();
+ if (currentNode.parent != null) {
+ currentNode = currentNode.parent;
+ } else {
+ currentNode = node;
+ }
+ }
+ }
+
+ public static void printNode(int indent, Node node, StringBuilder builder) {
+ if (DEBUG_LAYOUT_ON) {
+ StringBuilder indentationBuilder = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ indentationBuilder.append("| ");
+ }
+ String indentation = indentationBuilder.toString();
+
+ if (node.list.size() > 0) {
+ builder.append(indentation).append(node.name).append("\n");
+ for (Node c : node.list) {
+ printNode(indent + 1, c, builder);
+ }
+ builder.append(indentation).append(node.endString).append("\n");
+ } else {
+ if (node instanceof LogNode) {
+ builder.append(indentation).append(" ").append(node.name).append("\n");
+ } else {
+ builder.append(indentation).append("-- ").append(node.name)
+ .append(" : ").append(node.endString).append("\n");
+ }
+ }
+ }
+ }
+
+ public static void display() {
+ if (DEBUG_LAYOUT_ON) {
+ StringBuilder builder = new StringBuilder();
+ printNode(0, node, builder);
+ System.out.println("\n" + builder.toString());
+ }
+ }
+}
+
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
index 1f04a44..79ef16b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
@@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.widget.remotecompose.core.operations.layout.utils;
-package com.android.systemui.activatable
-
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+/**
+ * Basic interface for a lambda (used for logging)
+ */
+public interface StringValueSupplier {
+ /**
+ * returns a string value
+ * @return a string
+ */
+ String getString();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index a7d0ac6..8186192 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -695,6 +695,29 @@
}
/**
+ * Set the color based the R,G,B,A values
+ * @param r red (0 to 255)
+ * @param g green (0 to 255)
+ * @param b blue (0 to 255)
+ * @param a alpha (0 to 255)
+ */
+ public void setColor(int r, int g, int b, int a) {
+ int color = (a << 24) | (r << 16) | (g << 8) | b;
+ setColor(color);
+ }
+
+ /**
+ * Set the color based the R,G,B,A values
+ * @param r red (0.0 to 1.0)
+ * @param g green (0.0 to 1.0)
+ * @param b blue (0.0 to 1.0)
+ * @param a alpha (0.0 to 1.0)
+ */
+ public void setColor(float r, float g, float b, float a) {
+ setColor((int) r * 255, (int) g * 255, (int) b * 255, (int) a * 255);
+ }
+
+ /**
* Set the Color based on ID
* @param color
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
index 23c3ec5..b2d714e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
@@ -54,7 +54,7 @@
/**
* Put a item in the map
*
- * @param key item'values key
+ * @param key item's key
* @param value item's value
* @return old value if exist
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
index 221014c..606dc78 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
@@ -53,7 +53,7 @@
/**
* Put a item in the map
*
- * @param key item'values key
+ * @param key item's key
* @param value item's value
* @return old value if exist
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
index 4c1389c..a4fce80 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
@@ -400,8 +400,7 @@
-1, // no op
2, 2, 2, 2, 2, // + - * / %
2, 2, 2, 2, 2, 2, 2, 2, 2, //<<, >> , >>> , | , &, ^, min max
- 1, 1, 1, 1, 1, 1, // neg, abs, ++, -- , not , sign
-
+ 1, 1, 1, 1, 1, 1, // neg, abs, ++, -- , not , sign
3, 3, 3, // clamp, ifElse, mad,
0, 0, 0 // mad, ?:,
// a[0],a[1],a[2]
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
new file mode 100644
index 0000000..fb90781
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
@@ -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.internal.widget.remotecompose.core.operations.utilities;
+
+/**
+ * Utility serializer maintaining an indent buffer
+ */
+public class StringSerializer {
+ StringBuffer mBuffer = new StringBuffer();
+ String mIndentBuffer = " ";
+
+ /**
+ * Append some content to the current buffer
+ * @param indent the indentation level to use
+ * @param content content to append
+ */
+ public void append(int indent, String content) {
+ String indentation = mIndentBuffer.substring(0, indent);
+ mBuffer.append(indentation);
+ mBuffer.append(indentation);
+ mBuffer.append(content);
+ mBuffer.append("\n");
+ }
+
+ /**
+ * Reset the buffer
+ */
+ public void reset() {
+ mBuffer = new StringBuffer();
+ }
+
+ /**
+ * Return a string representation of the buffer
+ * @return string representation
+ */
+ public String toString() {
+ return mBuffer.toString();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
index 693deaf..50a7d59 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -18,7 +18,7 @@
/**
* Provides and interface to create easing functions
*/
-public class GeneralEasing extends Easing{
+public class GeneralEasing extends Easing{
float[] mEasingData = new float[0];
Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
index a42c584..65a337e 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -18,6 +18,7 @@
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.RemoteComposeBuffer;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import java.io.InputStream;
@@ -113,5 +114,13 @@
return mDocument.getNamedColors();
}
+ /**
+ * Return a component associated with id
+ * @param id the component id
+ * @return the corresponding component or null if not found
+ */
+ public Component getComponent(int id) {
+ return mDocument.getComponent(id);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index 39a770a..e01dd17 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -65,6 +65,21 @@
this.mCanvas = canvas;
}
+ @Override
+ public void save() {
+ mCanvas.save();
+ }
+
+ @Override
+ public void saveLayer(float x, float y, float width, float height) {
+ mCanvas.saveLayer(x, y, x + width, y + height, mPaint);
+ }
+
+ @Override
+ public void restore() {
+ mCanvas.restore();
+ }
+
/**
* Draw an image onto the canvas
*
@@ -613,6 +628,19 @@
}
@Override
+ public void roundedClipRect(float width, float height,
+ float topStart, float topEnd,
+ float bottomStart, float bottomEnd) {
+ Path roundedPath = new Path();
+ float[] radii = new float[] { topStart, topStart,
+ topEnd, topEnd, bottomEnd, bottomEnd, bottomStart, bottomStart};
+
+ roundedPath.addRoundRect(0f, 0f, width, height,
+ radii, android.graphics.Path.Direction.CW);
+ mCanvas.clipPath(roundedPath);
+ }
+
+ @Override
public void clipPath(int pathId, int regionOp) {
Path path = getPath(pathId, 0, 1);
if (regionOp == ClipPath.DIFFERENCE) {
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index a2f79cc..0d7f97a 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -215,6 +215,7 @@
}
int w = measureDimension(widthMeasureSpec, mDocument.getWidth());
int h = measureDimension(heightMeasureSpec, mDocument.getHeight());
+ mDocument.getDocument().invalidateMeasure();
if (!USE_VIEW_AREA_CLICK) {
if (mDocument.getDocument().getContentSizing() == RootContentBehavior.SIZING_SCALE) {
@@ -235,6 +236,8 @@
if (mDocument == null) {
return;
}
+ mARContext.setAnimationEnabled(true);
+ mARContext.currentTime = System.currentTimeMillis();
mARContext.setDebug(mDebug);
mARContext.useCanvas(canvas);
mARContext.mWidth = getWidth();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index ca984c0..2abdd57 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -258,6 +258,7 @@
"com_android_internal_content_om_OverlayManagerImpl.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
+ "com_android_internal_os_DebugStore.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_KernelAllocationStats.cpp",
"com_android_internal_os_KernelCpuBpfTracking.cpp",
@@ -315,6 +316,7 @@
"libcrypto",
"libcutils",
"libdebuggerd_client",
+ "libdebugstore_cxx",
"libutils",
"libbinder",
"libbinderdebug",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ed59327..03b5143a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -202,6 +202,7 @@
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
+extern int register_com_android_internal_os_DebugStore(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_KernelAllocationStats(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
@@ -1599,6 +1600,7 @@
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
+ REG_JNI(register_com_android_internal_os_DebugStore),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
REG_JNI(register_com_android_internal_os_LongMultiStateCounter),
REG_JNI(register_com_android_internal_os_Zygote),
diff --git a/core/jni/com_android_internal_os_DebugStore.cpp b/core/jni/com_android_internal_os_DebugStore.cpp
new file mode 100644
index 0000000..874d6ea
--- /dev/null
+++ b/core/jni/com_android_internal_os_DebugStore.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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 <debugstore/debugstore_cxx_bridge.rs.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+#include <iterator>
+#include <sstream>
+#include <vector>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jmethodID mGet;
+ jmethodID mSize;
+} gListClassInfo;
+
+static std::vector<std::string> list_to_vector(JNIEnv* env, jobject jList) {
+ std::vector<std::string> vec;
+ jint size = env->CallIntMethod(jList, gListClassInfo.mSize);
+ if (size % 2 != 0) {
+ std::ostringstream oss;
+
+ std::copy(vec.begin(), vec.end(), std::ostream_iterator<std::string>(oss, ", "));
+ ALOGW("DebugStore list size is odd: %d, elements: %s", size, oss.str().c_str());
+
+ return vec;
+ }
+
+ vec.reserve(size);
+
+ for (jint i = 0; i < size; i++) {
+ ScopedLocalRef<jstring> jEntry(env,
+ reinterpret_cast<jstring>(
+ env->CallObjectMethod(jList, gListClassInfo.mGet,
+ i)));
+ ScopedUtfChars cEntry(env, jEntry.get());
+ vec.emplace_back(cEntry.c_str());
+ }
+ return vec;
+}
+
+static void com_android_internal_os_DebugStore_endEvent(JNIEnv* env, jclass clazz, jlong eventId,
+ jobject jAttributeList) {
+ auto attributes = list_to_vector(env, jAttributeList);
+ debugstore::debug_store_end(static_cast<uint64_t>(eventId), attributes);
+}
+
+static jlong com_android_internal_os_DebugStore_beginEvent(JNIEnv* env, jclass clazz,
+ jstring jeventName,
+ jobject jAttributeList) {
+ ScopedUtfChars eventName(env, jeventName);
+ auto attributes = list_to_vector(env, jAttributeList);
+ jlong eventId =
+ static_cast<jlong>(debugstore::debug_store_begin(eventName.c_str(), attributes));
+ return eventId;
+}
+
+static void com_android_internal_os_DebugStore_recordEvent(JNIEnv* env, jclass clazz,
+ jstring jeventName,
+ jobject jAttributeList) {
+ ScopedUtfChars eventName(env, jeventName);
+ auto attributes = list_to_vector(env, jAttributeList);
+ debugstore::debug_store_record(eventName.c_str(), attributes);
+}
+
+static const JNINativeMethod gDebugStoreMethods[] = {
+ /* name, signature, funcPtr */
+ {"beginEventNative", "(Ljava/lang/String;Ljava/util/List;)J",
+ (void*)com_android_internal_os_DebugStore_beginEvent},
+ {"endEventNative", "(JLjava/util/List;)V",
+ (void*)com_android_internal_os_DebugStore_endEvent},
+ {"recordEventNative", "(Ljava/lang/String;Ljava/util/List;)V",
+ (void*)com_android_internal_os_DebugStore_recordEvent},
+};
+
+int register_com_android_internal_os_DebugStore(JNIEnv* env) {
+ int res = RegisterMethodsOrDie(env, "com/android/internal/os/DebugStore", gDebugStoreMethods,
+ NELEM(gDebugStoreMethods));
+ jclass listClass = FindClassOrDie(env, "java/util/List");
+ gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
+ gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
+
+ return res;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index e5ced25..e795e809 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -69,6 +69,7 @@
// 0 = no, 1 = yes
optional SettingProto window_orientation_listener_log = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_key_presses = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto touchpad_visualizer = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional DevOptions developer_options = 7;
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/Android.bp b/core/res/Android.bp
index 9207aa8..e900eb2 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -164,6 +164,7 @@
"com.android.window.flags.window-aconfig",
"android.permission.flags-aconfig",
"android.os.flags-aconfig",
+ "android.media.tv.flags-aconfig",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f3dac23..7aeabee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5609,7 +5609,8 @@
@hide
-->
<permission android:name="android.permission.ALWAYS_BOUND_TV_INPUT"
- android:protectionLevel="signature|privileged|vendorPrivileged" />
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.tis_always_bound_permission"/>
<!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
to ensure that only the system can bind to it.
@@ -6107,9 +6108,8 @@
android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
- <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
- Allows an application to send and receive messages via CDM transports.
- -->
+ <!-- Allows an application to send and receive messages via CDM transports.
+ @hide -->
<permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
android:protectionLevel="signature" />
@@ -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/TEST_MAPPING b/core/res/TEST_MAPPING
index 4d09076..0e01a2a 100644
--- a/core/res/TEST_MAPPING
+++ b/core/res/TEST_MAPPING
@@ -11,5 +11,22 @@
}
]
}
+ ],
+ // v2/sysui/suite/test-mapping-sysui-screenshot-test
+ "sysui-screenshot-test": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
]
-}
\ No newline at end of file
+}
diff --git a/core/res/res/drawable/ic_call_answer_video.xml b/core/res/res/drawable/ic_call_answer_video.xml
index 77c8892..79af247 100644
--- a/core/res/res/drawable/ic_call_answer_video.xml
+++ b/core/res/res/drawable/ic_call_answer_video.xml
@@ -16,8 +16,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="20"
- android:viewportHeight="20"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal"
android:autoMirrored="true">
<path
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/config.xml b/core/res/res/values/config.xml
index 495af5b..df288f9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3065,10 +3065,13 @@
frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
<integer name="config_userTypePackageWhitelistMode">13</integer> <!-- 1+4+8 -->
- <!-- Whether the main user is a permanent admin user. If the main user is a permanent admin user
- it can't be deleted or downgraded to non-admin status.
- This is generally only relevant on headless system user mode devices; on other devices, the
- main user is the system user which is always a permanent admin anyway. -->
+ <!-- Whether the device will automatically (at first boot) have a designated main user and treat
+ it as a permanent admin.
+ Since the main user is a permanent admin user it can't be deleted or downgraded to
+ non-admin status.
+ This is generally only relevant on headless system user mode (HSUM) devices; on other
+ devices, the main user is the system user which is always a permanent admin anyway.
+ Note that HSUM devices without this enabled will not automatically have a main user. -->
<bool name="config_isMainUserPermanentAdmin">true</bool>
<!-- Whether switch to headless system user is allowed. If allowed,
@@ -3079,7 +3082,7 @@
<bool name="config_enableMultiUserUI">false</bool>
<!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
- with admin privileges and admin privileges can be granted/revoked from existing users. -->
+ with admin privileges and admin privileges can be granted/revoked from existing users. -->
<bool name="config_enableMultipleAdmins">false</bool>
<!-- Whether there is a communal profile which should always be running.
@@ -3961,6 +3964,9 @@
flag does not exist -->
<bool name="config_magnification_always_on_enabled">true</bool>
+ <!-- Whether to keep fullscreen magnification zoom level when context changes. -->
+ <bool name="config_magnification_keep_zoom_level_when_context_changed">false</bool>
+
<!-- If true, the display will be shifted around in ambient mode. -->
<bool name="config_enableBurnInProtection">false</bool>
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 e94db2d..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>
@@ -6496,10 +6499,14 @@
<string name="screen_not_shared_sensitive_content">App content hidden from screen share for security</string>
<!-- Satellite related messages -->
- <!-- Notification title when satellite service is connected. -->
+ <!-- Notification title when satellite service is auto connected. -->
<string name="satellite_notification_title">Auto connected to satellite</string>
- <!-- Notification summary when satellite service is connected. [CHAR LIMIT=NONE] -->
+ <!-- Notification summary when satellite service is auto connected. [CHAR LIMIT=NONE] -->
<string name="satellite_notification_summary">You can send and receive messages without a mobile or Wi-Fi network</string>
+ <!-- Notification title when satellite service can be manually enabled. -->
+ <string name="satellite_notification_manual_title">Use satellite messaging?</string>
+ <!-- Notification summary when satellite service can be manually enabled. [CHAR LIMIT=NONE] -->
+ <string name="satellite_notification_manual_summary">Send and receive messages without a mobile or Wi-Fi network</string>
<!-- Invoke "What to expect" dialog of messaging application -->
<string name="satellite_notification_open_message">Open Messages</string>
<!-- Invoke Satellite setting activity of Settings -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8734b44..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" />
@@ -4779,6 +4780,7 @@
<java-symbol type="bool" name="config_magnification_area" />
<java-symbol type="bool" name="config_magnification_always_on_enabled" />
+ <java-symbol type="bool" name="config_magnification_keep_zoom_level_when_context_changed" />
<java-symbol type="bool" name="config_trackerAppNeedsPermissions"/>
<!-- FullScreenMagnification thumbnail -->
@@ -5460,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" />
@@ -5499,6 +5503,8 @@
<!-- System notification for satellite service -->
<java-symbol type="string" name="satellite_notification_title" />
<java-symbol type="string" name="satellite_notification_summary" />
+ <java-symbol type="string" name="satellite_notification_manual_title" />
+ <java-symbol type="string" name="satellite_notification_manual_summary" />
<java-symbol type="string" name="satellite_notification_open_message" />
<java-symbol type="string" name="satellite_notification_how_it_works" />
<java-symbol type="drawable" name="ic_satellite_alt_24px" />
diff --git a/core/res/res/xml/haptic_feedback_customization.xml b/core/res/res/xml/haptic_feedback_customization.xml
new file mode 100644
index 0000000..7ac0787
--- /dev/null
+++ b/core/res/res/xml/haptic_feedback_customization.xml
@@ -0,0 +1,18 @@
+<?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.
+ -->
+
+<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/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index bd9c4b8..519f23b 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -28,6 +28,7 @@
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Printer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -43,10 +44,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -400,4 +404,138 @@
}
assertFalse(allowed);
}
+
+ /** Dumpsys information about a single database. */
+
+ /**
+ * Collect and parse dumpsys output. This is not a full parser. It is only enough to support
+ * the unit tests.
+ */
+ private static class Dumpsys {
+ // Regular expressions for parsing the output. Reportedly, regular expressions are
+ // expensive, so these are created only if a dumpsys object is created.
+ private static final Object sLock = new Object();
+ static Pattern mPool;
+ static Pattern mConnection;
+ static Pattern mEntry;
+ static Pattern mSingleWord;
+ static Pattern mNone;
+
+ // The raw strings read from dumpsys. Once loaded, this list never changes.
+ final ArrayList<String> mRaw = new ArrayList<>();
+
+ // Parsed dumpsys. This contains only the bits that are being tested.
+ static class Connection {
+ ArrayList<String> mRecent = new ArrayList<>();
+ ArrayList<String> mLong = new ArrayList<>();
+ }
+ static class Database {
+ String mPath;
+ ArrayList<Connection> mConnection = new ArrayList<>();
+ }
+ ArrayList<Database> mDatabase;
+ ArrayList<String> mConcurrent;
+
+ Dumpsys() {
+ SQLiteDebug.dump(
+ new Printer() { public void println(String x) { mRaw.add(x); } },
+ new String[0]);
+ parse();
+ }
+
+ /** Parse the raw text. Return true if no errors were detected. */
+ boolean parse() {
+ initialize();
+
+ // Reset the parsed information. This method may be called repeatedly.
+ mDatabase = new ArrayList<>();
+ mConcurrent = new ArrayList<>();
+
+ Database current = null;
+ Connection connection = null;
+ Matcher matcher;
+ for (int i = 0; i < mRaw.size(); i++) {
+ final String line = mRaw.get(i);
+ matcher = mPool.matcher(line);
+ if (matcher.lookingAt()) {
+ current = new Database();
+ mDatabase.add(current);
+ current.mPath = matcher.group(1);
+ continue;
+ }
+ matcher = mConnection.matcher(line);
+ if (matcher.lookingAt()) {
+ connection = new Connection();
+ current.mConnection.add(connection);
+ continue;
+ }
+
+ if (line.contains("Most recently executed operations")) {
+ i += readTable(connection.mRecent, i, mEntry);
+ continue;
+ }
+
+ if (line.contains("Operations exceeding 2000ms")) {
+ i += readTable(connection.mLong, i, mEntry);
+ continue;
+ }
+ if (line.contains("Concurrently opened database files")) {
+ i += readTable(mConcurrent, i, mSingleWord);
+ continue;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Read a series of lines following a header. Return the number of lines read. The input
+ * line number is the number of the header.
+ */
+ private int readTable(List<String> s, int header, Pattern p) {
+ // Special case: if the first line is "<none>" then there are no more lines to the
+ // table.
+ if (lookingAt(header+1, mNone)) return 1;
+
+ int i;
+ for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) {
+ s.add(mRaw.get(i).trim());
+ }
+ return i - header;
+ }
+
+ /** Return true if the n'th raw line matches the pattern. */
+ boolean lookingAt(int n, Pattern p) {
+ return p.matcher(mRaw.get(n)).lookingAt();
+ }
+
+ /** Compile the regular expressions the first time. */
+ private static void initialize() {
+ synchronized (sLock) {
+ if (mPool != null) return;
+ mPool = Pattern.compile("Connection pool for (\\S+):");
+ mConnection = Pattern.compile("\\s+Connection #(\\d+):");
+ mEntry = Pattern.compile("\\s+(\\d+): ");
+ mSingleWord = Pattern.compile(" (\\S+)$");
+ mNone = Pattern.compile("\\s+<none>$");
+ }
+ }
+ }
+
+ @Test
+ public void testDumpsys() throws Exception {
+ Dumpsys dumpsys = new Dumpsys();
+
+ assertEquals(1, dumpsys.mDatabase.size());
+ // Note: cannot test mConcurrent because that attribute is not hermitic with respect to
+ // the tests.
+
+ Dumpsys.Database db = dumpsys.mDatabase.get(0);
+
+ // Work with normalized paths.
+ String wantPath = mDatabaseFile.toPath().toRealPath().toString();
+ String realPath = new File(db.mPath).toPath().toRealPath().toString();
+ assertEquals(wantPath, realPath);
+
+ assertEquals(1, db.mConnection.size());
+ }
}
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/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
index 5464ea3..d6c0e99 100644
--- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -16,7 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.BiometricPrompt.MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER;
import static android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.MAX_DESCRIPTION_CHARACTER_NUMBER;
import static android.hardware.biometrics.PromptVerticalListContentView.MAX_EACH_ITEM_CHARACTER_NUMBER;
import static android.hardware.biometrics.PromptVerticalListContentView.MAX_ITEM_NUMBER;
@@ -117,19 +116,7 @@
() -> new BiometricPrompt.Builder(mContext).setLogoDescription(null)
);
- assertThat(e).hasMessageThat().contains(
- "Logo description passed in can not be null or exceed");
- }
-
- @Test
- public void testLogoDescription_charLimit() {
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> new BiometricPrompt.Builder(mContext).setLogoDescription(
- generateRandomString(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + 1))
- );
-
- assertThat(e).hasMessageThat().contains(
- "Logo description passed in can not be null or exceed");
+ assertThat(e).hasMessageThat().isEqualTo("Logo description passed in can not be null");
}
@Test
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 66b22a8..774878a 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -54,7 +54,7 @@
import static org.junit.Assert.fail;
import android.os.FileUtils.MemoryPipe;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DocumentsContract.Document;
import android.system.Os;
@@ -156,7 +156,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
+ @DisabledOnRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_FileToPipe() throws Exception {
for (int size : DATA_SIZES) {
final File src = new File(mTarget, "src");
@@ -177,7 +177,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
+ @DisabledOnRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_PipeToFile() throws Exception {
for (int size : DATA_SIZES) {
final File dest = new File(mTarget, "dest");
@@ -197,7 +197,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
+ @DisabledOnRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_PipeToPipe() throws Exception {
for (int size : DATA_SIZES) {
byte[] expected = new byte[size];
@@ -215,7 +215,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
+ @DisabledOnRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_ShortPipeToFile() throws Exception {
byte[] source = new byte[33_000_000];
new Random().nextBytes(source);
@@ -257,9 +257,9 @@
assertArrayEquals(expected, actual);
}
- //TODO(ravenwood) Remove the _$noRavenwood suffix and add @RavenwoodIgnore instead
@Test
- public void testCopy_SocketToFile_FileToSocket$noRavenwood() throws Exception {
+ @DisabledOnRavenwood(reason = "Missing Os methods in Ravenwood")
+ public void testCopy_SocketToFile_FileToSocket() throws Exception {
for (int size : DATA_SIZES ) {
final File src = new File(mTarget, "src");
final File dest = new File(mTarget, "dest");
@@ -510,7 +510,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
+ @DisabledOnRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_normal() throws Exception {
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
@@ -530,7 +530,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
+ @DisabledOnRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_unknown() throws Exception {
assertNameEquals("test",
FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
@@ -544,7 +544,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
+ @DisabledOnRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_dir() throws Exception {
assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
new File(mTarget, "test").mkdir();
@@ -559,7 +559,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
+ @DisabledOnRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_increment() throws Exception {
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
new File(mTarget, "test.jpg").createNewFile();
@@ -579,7 +579,7 @@
}
@Test
- @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
+ @DisabledOnRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_mimeless() throws Exception {
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg"));
new File(mTarget, "test.jpg").createNewFile();
@@ -675,8 +675,7 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "Requires kernel support")
- public void testTranslateMode() throws Exception {
+ public void testTranslateMode() {
assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
assertTranslate("rw", O_RDWR | O_CREAT,
@@ -695,8 +694,7 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "Requires kernel support")
- public void testMalformedTransate_int() throws Exception {
+ public void testMalformedTransate_int() {
try {
// The non-standard Linux access mode 3 should throw
// an IllegalArgumentException.
@@ -707,8 +705,7 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "Requires kernel support")
- public void testMalformedTransate_string() throws Exception {
+ public void testMalformedTransate_string() {
try {
// The non-standard Linux access mode 3 should throw
// an IllegalArgumentException.
@@ -719,8 +716,7 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "Requires kernel support")
- public void testTranslateMode_Invalid() throws Exception {
+ public void testTranslateMode_Invalid() {
try {
translateModeStringToPosix("rwx");
fail();
@@ -734,8 +730,7 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "Requires kernel support")
- public void testTranslateMode_Access() throws Exception {
+ public void testTranslateMode_Access() {
assertEquals(O_RDONLY, translateModeAccessToPosix(F_OK));
assertEquals(O_RDONLY, translateModeAccessToPosix(R_OK));
assertEquals(O_WRONLY, translateModeAccessToPosix(W_OK));
@@ -744,7 +739,7 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "Requires kernel support")
+ @DisabledOnRavenwood(reason = "Requires kernel support")
public void testConvertToModernFd() throws Exception {
final String nonce = String.valueOf(System.nanoTime());
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/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 12f8c9c..f9da832 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -240,4 +240,45 @@
verify(mockNoIconMenu, times(0)).setIcon(any());
verify(mockNoIconMenu2, times(0)).setIcon(any());
}
+
+ @UiThreadTest
+ @Test
+ public void testAutofillMenuItemEnabledWhenNoTextSelected() {
+ ContextMenu menu = mock(ContextMenu.class);
+ MenuItem mockMenuItem = newMockMenuItem();
+ when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mockMenuItem);
+ MenuItem mockAutofillMenuItem = newMockMenuItem();
+ when(menu.add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()))
+ .thenReturn(mockAutofillMenuItem);
+
+ EditText et = mActivity.findViewById(R.id.editText);
+ et.setText("Test");
+
+ Editor editor = et.getEditorForTesting();
+ editor.onCreateContextMenu(menu);
+
+ verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt());
+ verify(mockAutofillMenuItem).setEnabled(true);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testAutofillMenuItemNotEnabledWhenTextSelected() {
+ ContextMenu menu = mock(ContextMenu.class);
+ MenuItem mockMenuItem = newMockMenuItem();
+ when(menu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mockMenuItem);
+ MenuItem mockAutofillMenuItem = newMockMenuItem();
+ when(menu.add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()))
+ .thenReturn(mockAutofillMenuItem);
+
+ EditText et = mActivity.findViewById(R.id.editText);
+ et.setText("Test");
+ et.selectAll();
+ Editor editor = et.getEditorForTesting();
+ editor.onCreateContextMenu(menu);
+
+ verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt());
+ verify(mockAutofillMenuItem).setEnabled(false);
+ }
+
}
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/coretests/src/com/android/internal/os/DebugStoreTest.java b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
new file mode 100644
index 0000000..786c2fc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/**
+ * Test class for {@link DebugStore}.
+ *
+ * To run it:
+ * atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
+ */
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = DebugStore.class)
+@SmallTest
+public class DebugStoreTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Mock
+ private DebugStore.DebugStoreNative mDebugStoreNativeMock;
+
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ DebugStore.setDebugStoreNative(mDebugStoreNativeMock);
+ }
+
+ @Test
+ public void testRecordServiceOnStart() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidService"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(1L);
+
+ long eventId = DebugStore.recordServiceOnStart(1, 0, intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "com.android.ACTION",
+ "comp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(1L);
+ }
+
+ @Test
+ public void testRecordServiceCreate() {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.name = "androidService";
+ serviceInfo.packageName = "com.android";
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(2L);
+
+ long eventId = DebugStore.recordServiceCreate(serviceInfo);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "name", "androidService",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(2L);
+ }
+
+ @Test
+ public void testRecordServiceBind() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidService"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(3L);
+
+ long eventId = DebugStore.recordServiceBind(true, intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "rebind", "true",
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(3L);
+ }
+
+ @Test
+ public void testRecordGoAsync() {
+ DebugStore.recordGoAsync("androidReceiver");
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "androidReceiver"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordFinish() {
+ DebugStore.recordFinish("androidReceiver");
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "androidReceiver"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordLongLooperMessage() {
+ DebugStore.recordLongLooperMessage(100, "androidHandler", 500L);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "code", "100",
+ "trgt", "androidHandler",
+ "elapsed", "500"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordBroadcastHandleReceiver() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidReceiver"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(4L);
+
+ long eventId = DebugStore.recordBroadcastHandleReceiver(intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidReceiver}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(4L);
+ }
+
+ @Test
+ public void testRecordEventEnd() {
+ DebugStore.recordEventEnd(1L);
+
+ verify(mDebugStoreNativeMock).endEvent(eq(1L), anyList());
+ }
+
+ @Test
+ public void testRecordServiceOnStartWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
+
+ long eventId = DebugStore.recordServiceOnStart(1, 0, null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "null",
+ "comp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(5L);
+ }
+
+ @Test
+ public void testRecordServiceCreateWithNullServiceInfo() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
+
+ long eventId = DebugStore.recordServiceCreate(null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "name", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(6L);
+ }
+
+ @Test
+ public void testRecordServiceBindWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
+
+ long eventId = DebugStore.recordServiceBind(false, null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "rebind", "false",
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(7L);
+ }
+
+ @Test
+ public void testRecordBroadcastHandleReceiverWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
+
+ long eventId = DebugStore.recordBroadcastHandleReceiver(null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(8L);
+ }
+
+ @Test
+ public void testRecordGoAsyncWithNullReceiverClassName() {
+ DebugStore.recordGoAsync(null);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "null"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordFinishWithNullReceiverClassName() {
+ DebugStore.recordFinish(null);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "null"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordLongLooperMessageWithNullTargetClass() {
+ DebugStore.recordLongLooperMessage(200, null, 1000L);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "code", "200",
+ "trgt", "null",
+ "elapsed", "1000"
+ ).inOrder();
+ }
+}
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/data/keyboards/Vendor_054c_Product_05c4.idc b/data/keyboards/Vendor_054c_Product_05c4.idc
index 2da6227..45b5207 100644
--- a/data/keyboards/Vendor_054c_Product_05c4.idc
+++ b/data/keyboards/Vendor_054c_Product_05c4.idc
@@ -51,7 +51,7 @@
# fingers, it prevents tapping to click because it thinks the finger's moving
# too fast.
#
-# Since this touchpad doesn't seem to have to drumroll issues, we can safely
+# Since this touchpad doesn't seem to have drumroll issues, we can safely
# disable drumroll detection.
gestureProp.Drumroll_Suppression_Enable = 0
@@ -60,3 +60,11 @@
# from the palm classifier to increase the usable area of the pad.
gestureProp.Palm_Edge_Zone_Width = 0
gestureProp.Tap_Exclusion_Border_Width = 0
+
+# Touchpad is small, scale up the pointer movements to make it more practical
+# to use.
+gestureProp.Point_X_Out_Scale = 2.5
+gestureProp.Point_Y_Out_Scale = 2.5
+
+# TODO(b/351326684): Ideally "Scroll X Out Scale" and "Scroll Y Out Scale"
+# should be adjusted as well. Currently not supported in IDC files.
diff --git a/data/keyboards/Vendor_054c_Product_09cc.idc b/data/keyboards/Vendor_054c_Product_09cc.idc
index 2a1a4fc..45b5207 100644
--- a/data/keyboards/Vendor_054c_Product_09cc.idc
+++ b/data/keyboards/Vendor_054c_Product_09cc.idc
@@ -60,3 +60,11 @@
# from the palm classifier to increase the usable area of the pad.
gestureProp.Palm_Edge_Zone_Width = 0
gestureProp.Tap_Exclusion_Border_Width = 0
+
+# Touchpad is small, scale up the pointer movements to make it more practical
+# to use.
+gestureProp.Point_X_Out_Scale = 2.5
+gestureProp.Point_Y_Out_Scale = 2.5
+
+# TODO(b/351326684): Ideally "Scroll X Out Scale" and "Scroll Y Out Scale"
+# should be adjusted as well. Currently not supported in IDC files.
diff --git a/data/keyboards/Vendor_054c_Product_0ce6.idc b/data/keyboards/Vendor_054c_Product_0ce6.idc
new file mode 100644
index 0000000..48027e7
--- /dev/null
+++ b/data/keyboards/Vendor_054c_Product_0ce6.idc
@@ -0,0 +1,31 @@
+# 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.
+
+#
+# Sony Playstation(R) DualSense 5 Controller
+#
+
+## Touchpad ##
+
+# Because of the way this touchpad is positioned, touches around the edges are
+# no more likely to be palms than ones in the middle, so remove the edge zones
+# from the palm classifier to increase the usable area of the pad.
+gestureProp.Palm_Edge_Zone_Width = 0
+gestureProp.Tap_Exclusion_Border_Width = 0
+
+gestureProp.Point_X_Out_Scale = 2.0
+gestureProp.Point_Y_Out_Scale = 2.0
+
+# TODO(b/351326684): Ideally "Scroll X Out Scale" and "Scroll Y Out Scale"
+# should be adjusted as well. Currently not supported in IDC files.
diff --git a/data/keyboards/Vendor_054c_Product_0df2.idc b/data/keyboards/Vendor_054c_Product_0df2.idc
new file mode 100644
index 0000000..4bcf0be
--- /dev/null
+++ b/data/keyboards/Vendor_054c_Product_0df2.idc
@@ -0,0 +1,31 @@
+# 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.
+
+#
+# Sony Playstation(R) DualSense Edge 5 Controller
+#
+
+## Touchpad ##
+
+# Because of the way this touchpad is positioned, touches around the edges are
+# no more likely to be palms than ones in the middle, so remove the edge zones
+# from the palm classifier to increase the usable area of the pad.
+gestureProp.Palm_Edge_Zone_Width = 0
+gestureProp.Tap_Exclusion_Border_Width = 0
+
+gestureProp.Point_X_Out_Scale = 2.0
+gestureProp.Point_Y_Out_Scale = 2.0
+
+# TODO(b/351326684): Ideally "Scroll X Out Scale" and "Scroll Y Out Scale"
+# should be adjusted as well. Currently not supported in IDC files.
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/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index b2bc3de..37f0067 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -18,8 +18,8 @@
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
-import static androidx.window.common.CommonFoldingFeature.parseListFromString;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.layout.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
import android.content.Context;
@@ -31,6 +31,9 @@
import android.util.Log;
import android.util.SparseIntArray;
+import androidx.window.common.layout.CommonFoldingFeature;
+import androidx.window.common.layout.DisplayFoldFeatureCommon;
+
import com.android.internal.R;
import java.util.ArrayList;
@@ -200,6 +203,23 @@
/**
+ * Returns the list of supported {@link DisplayFoldFeatureCommon} calculated from the
+ * {@link DeviceStateManagerFoldingFeatureProducer}.
+ */
+ @NonNull
+ public List<DisplayFoldFeatureCommon> getDisplayFeatures() {
+ final List<DisplayFoldFeatureCommon> foldFeatures = new ArrayList<>();
+ final List<CommonFoldingFeature> folds = getFoldsWithUnknownState();
+
+ final boolean isHalfOpenedSupported = isHalfOpenedSupported();
+ for (CommonFoldingFeature fold : folds) {
+ foldFeatures.add(DisplayFoldFeatureCommon.create(fold, isHalfOpenedSupported));
+ }
+ return foldFeatures;
+ }
+
+
+ /**
* Returns {@code true} if the device supports half-opened mode, {@code false} otherwise.
*/
public boolean isHalfOpenedSupported() {
@@ -211,7 +231,7 @@
* @param storeFeaturesConsumer a consumer to collect the data when it is first available.
*/
@Override
- public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
+ public void getData(@NonNull Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
mRawFoldSupplier.getData((String displayFeaturesString) -> {
if (TextUtils.isEmpty(displayFeaturesString)) {
storeFeaturesConsumer.accept(new ArrayList<>());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 6d758f1..9651918 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -26,6 +26,8 @@
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.window.common.layout.CommonFoldingFeature;
+
import com.android.internal.R;
import java.util.Optional;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/collections/ListUtil.java b/libs/WindowManager/Jetpack/src/androidx/window/common/collections/ListUtil.java
new file mode 100644
index 0000000..e72459f
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/collections/ListUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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 androidx.window.common.collections;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * A class to contain utility methods for {@link List}.
+ */
+public final class ListUtil {
+
+ private ListUtil() {}
+
+ /**
+ * Returns a new {@link List} that is created by applying the {@code transformer} to the
+ * {@code source} list.
+ */
+ public static <T, U> List<U> map(List<T> source, Function<T, U> transformer) {
+ final List<U> target = new ArrayList<>();
+ for (int i = 0; i < source.size(); i++) {
+ target.add(transformer.apply(source.get(i)));
+ }
+ return target;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
similarity index 99%
rename from libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
rename to libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
index b95bca1..85c4fe1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.common;
+package androidx.window.common.layout;
import static androidx.window.common.ExtensionHelper.isZero;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/layout/DisplayFoldFeatureCommon.java b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/DisplayFoldFeatureCommon.java
new file mode 100644
index 0000000..594bd9c
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/DisplayFoldFeatureCommon.java
@@ -0,0 +1,171 @@
+/*
+ * 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 androidx.window.common.layout;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A class that represents if a fold is part of the device.
+ */
+public final class DisplayFoldFeatureCommon {
+
+ /**
+ * Returns a new instance of {@link DisplayFoldFeatureCommon} based off of
+ * {@link CommonFoldingFeature} and whether or not half opened is supported.
+ */
+ public static DisplayFoldFeatureCommon create(CommonFoldingFeature foldingFeature,
+ boolean isHalfOpenedSupported) {
+ @FoldType
+ final int foldType;
+ if (foldingFeature.getType() == CommonFoldingFeature.COMMON_TYPE_HINGE) {
+ foldType = DISPLAY_FOLD_FEATURE_TYPE_HINGE;
+ } else {
+ foldType = DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN;
+ }
+
+ final Set<Integer> properties = new ArraySet<>();
+
+ if (isHalfOpenedSupported) {
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ }
+ return new DisplayFoldFeatureCommon(foldType, properties);
+ }
+
+ /**
+ * The type of fold is unknown. This is here for compatibility reasons if a new type is added,
+ * and cannot be reported to an incompatible application.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_TYPE_UNKNOWN = 0;
+
+ /**
+ * The type of fold is a physical hinge separating two display panels.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_TYPE_HINGE = 1;
+
+ /**
+ * The type of fold is a screen that folds from 0-180.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {DISPLAY_FOLD_FEATURE_TYPE_UNKNOWN, DISPLAY_FOLD_FEATURE_TYPE_HINGE,
+ DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN})
+ public @interface FoldType {
+ }
+
+ /**
+ * The fold supports the half opened state.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED = 1;
+
+ @IntDef(value = {DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED})
+ public @interface FoldProperty {
+ }
+
+ @FoldType
+ private final int mType;
+
+ private final Set<Integer> mProperties;
+
+ /**
+ * Creates an instance of [FoldDisplayFeature].
+ *
+ * @param type the type of fold, either [FoldDisplayFeature.TYPE_HINGE] or
+ * [FoldDisplayFeature.TYPE_FOLDABLE_SCREEN]
+ * @hide
+ */
+ public DisplayFoldFeatureCommon(@FoldType int type, @NonNull Set<Integer> properties) {
+ mType = type;
+ mProperties = new ArraySet<>();
+ assertPropertiesAreValid(properties);
+ mProperties.addAll(properties);
+ }
+
+ /**
+ * Returns the type of fold that is either a hinge or a fold.
+ */
+ @FoldType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns {@code true} if the fold has the given property, {@code false} otherwise.
+ */
+ public boolean hasProperty(@FoldProperty int property) {
+ return mProperties.contains(property);
+ }
+ /**
+ * Returns {@code true} if the fold has all the given properties, {@code false} otherwise.
+ */
+ public boolean hasProperties(@NonNull @FoldProperty int... properties) {
+ for (int i = 0; i < properties.length; i++) {
+ if (!mProperties.contains(properties[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a copy of the set of properties.
+ * @hide
+ */
+ public Set<Integer> getProperties() {
+ return new ArraySet<>(mProperties);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DisplayFoldFeatureCommon that = (DisplayFoldFeatureCommon) o;
+ return mType == that.mType && Objects.equals(mProperties, that.mProperties);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mProperties);
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayFoldFeatureCommon{mType=" + mType + ", mProperties=" + mProperties + '}';
+ }
+
+ private static void assertPropertiesAreValid(@NonNull Set<Integer> properties) {
+ for (int property : properties) {
+ if (!isProperty(property)) {
+ throw new IllegalArgumentException("Property is not a valid type: " + property);
+ }
+ }
+ }
+
+ private static boolean isProperty(int property) {
+ if (property == DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED) {
+ return true;
+ }
+ return false;
+ }
+}
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 409cde3..25e7107 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -89,9 +89,9 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.common.layout.CommonFoldingFeature;
import androidx.window.extensions.WindowExtensions;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.core.util.function.Function;
@@ -1334,13 +1334,24 @@
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
mPresenter.expandTaskFragment(wct, container);
- } else {
- // Put activity into a new expanded container.
- final TaskFragmentContainer newContainer =
- new TaskFragmentContainer.Builder(this, getTaskId(activity), activity)
- .setPendingAppearedActivity(activity).build();
- mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
+ return;
}
+
+ final SplitContainer splitContainer = getActiveSplitForContainer(container);
+ if (splitContainer instanceof SplitPinContainer
+ && !container.isPinned() && container.getRunningActivityCount() == 1) {
+ // This is already the expected state when the pinned container is shown with an
+ // expanded activity in a standalone container on the side. Moving the activity into
+ // another new expanded container again is not necessary and could result in
+ // recursively creating new TaskFragmentContainers if the activity somehow relaunched.
+ return;
+ }
+
+ // Put activity into a new expanded container.
+ final TaskFragmentContainer newContainer =
+ new TaskFragmentContainer.Builder(this, getTaskId(activity), activity)
+ .setPendingAppearedActivity(activity).build();
+ mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
}
/**
@@ -2714,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/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
index a0f481a..870c92e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
@@ -16,48 +16,24 @@
package androidx.window.extensions.layout;
-import androidx.window.common.CommonFoldingFeature;
-import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
-
-import java.util.ArrayList;
-import java.util.List;
+import androidx.window.common.layout.DisplayFoldFeatureCommon;
/**
* Util functions for working with {@link androidx.window.extensions.layout.DisplayFoldFeature}.
*/
-public class DisplayFoldFeatureUtil {
+public final class DisplayFoldFeatureUtil {
private DisplayFoldFeatureUtil() {}
- private static DisplayFoldFeature create(CommonFoldingFeature foldingFeature,
- boolean isHalfOpenedSupported) {
- final int foldType;
- if (foldingFeature.getType() == CommonFoldingFeature.COMMON_TYPE_HINGE) {
- foldType = DisplayFoldFeature.TYPE_HINGE;
- } else {
- foldType = DisplayFoldFeature.TYPE_SCREEN_FOLD_IN;
- }
- DisplayFoldFeature.Builder featureBuilder = new DisplayFoldFeature.Builder(foldType);
-
- if (isHalfOpenedSupported) {
- featureBuilder.addProperty(DisplayFoldFeature.FOLD_PROPERTY_SUPPORTS_HALF_OPENED);
- }
- return featureBuilder.build();
- }
-
/**
- * Returns the list of supported {@link DisplayFeature} calculated from the
- * {@link DeviceStateManagerFoldingFeatureProducer}.
+ * Returns a {@link DisplayFoldFeature} that matches the given {@link DisplayFoldFeatureCommon}.
*/
- public static List<DisplayFoldFeature> extractDisplayFoldFeatures(
- DeviceStateManagerFoldingFeatureProducer producer) {
- List<DisplayFoldFeature> foldFeatures = new ArrayList<>();
- List<CommonFoldingFeature> folds = producer.getFoldsWithUnknownState();
-
- final boolean isHalfOpenedSupported = producer.isHalfOpenedSupported();
- for (CommonFoldingFeature fold : folds) {
- foldFeatures.add(DisplayFoldFeatureUtil.create(fold, isHalfOpenedSupported));
+ public static DisplayFoldFeature translate(DisplayFoldFeatureCommon foldFeatureCommon) {
+ final DisplayFoldFeature.Builder builder =
+ new DisplayFoldFeature.Builder(foldFeatureCommon.getType());
+ for (int property: foldFeatureCommon.getProperties()) {
+ builder.addProperty(property);
}
- return foldFeatures;
+ return builder.build();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a3ef68a..f1ea19a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -19,11 +19,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.common.ExtensionHelper.isZero;
import static androidx.window.common.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.common.ExtensionHelper.transformToWindowSpaceRect;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_FLAT;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import android.app.Activity;
import android.app.ActivityThread;
@@ -45,9 +45,10 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
import androidx.annotation.VisibleForTesting;
-import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.common.collections.ListUtil;
+import androidx.window.common.layout.CommonFoldingFeature;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.util.DeduplicateConsumer;
@@ -95,8 +96,8 @@
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
mFoldingFeatureProducer = foldingFeatureProducer;
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
- final List<DisplayFoldFeature> displayFoldFeatures =
- DisplayFoldFeatureUtil.extractDisplayFoldFeatures(mFoldingFeatureProducer);
+ final List<DisplayFoldFeature> displayFoldFeatures = ListUtil.map(
+ mFoldingFeatureProducer.getDisplayFeatures(), DisplayFoldFeatureUtil::translate);
mSupportedWindowFeatures = new SupportedWindowFeatures.Builder(displayFoldFeatures).build();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index b63fd08..60bc7be 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -26,10 +26,10 @@
import androidx.annotation.NonNull;
import androidx.window.common.BaseDataProducer;
-import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.common.RawFoldingFeatureProducer;
+import androidx.window.common.layout.CommonFoldingFeature;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
index 4fd03e4..6e0e711 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
@@ -26,7 +26,7 @@
import android.graphics.Rect;
import android.os.IBinder;
-import androidx.window.common.CommonFoldingFeature;
+import androidx.window.common.layout.CommonFoldingFeature;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/collections/ListUtilTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/collections/ListUtilTest.java
new file mode 100644
index 0000000..a077bdf
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/collections/ListUtilTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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 androidx.window.common.collections;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for {@link ListUtil}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:ListUtil
+ */
+public class ListUtilTest {
+
+ @Test
+ public void test_map_empty_returns_empty() {
+ final List<String> emptyList = new ArrayList<>();
+ final List<Integer> result = ListUtil.map(emptyList, String::length);
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void test_map_maintains_order() {
+ final List<String> source = new ArrayList<>();
+ source.add("a");
+ source.add("aa");
+
+ final List<Integer> result = ListUtil.map(source, String::length);
+
+ assertThat(result).containsExactly(1, 2).inOrder();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/layout/DisplayFoldFeatureCommonTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/layout/DisplayFoldFeatureCommonTest.java
new file mode 100644
index 0000000..6c17851
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/layout/DisplayFoldFeatureCommonTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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 androidx.window.common.layout;
+
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_TYPE_FOLD;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_TYPE_HINGE;
+import static androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED;
+import static androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_HINGE;
+import static androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Rect;
+import android.util.ArraySet;
+
+import org.junit.Test;
+
+import java.util.Set;
+
+/**
+ * Test class for {@link DisplayFoldFeatureCommon}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:DisplayFoldFeatureCommonTest
+ */
+public class DisplayFoldFeatureCommonTest {
+
+ @Test
+ public void test_different_type_not_equals() {
+ final Set<Integer> properties = new ArraySet<>();
+ final DisplayFoldFeatureCommon first =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+ final DisplayFoldFeatureCommon second =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN, properties);
+
+ assertThat(first).isEqualTo(second);
+ }
+
+ @Test
+ public void test_different_property_set_not_equals() {
+ final Set<Integer> firstProperties = new ArraySet<>();
+ final Set<Integer> secondProperties = new ArraySet<>();
+ secondProperties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon first =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, firstProperties);
+ final DisplayFoldFeatureCommon second =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, secondProperties);
+
+ assertThat(first).isNotEqualTo(second);
+ }
+
+ @Test
+ public void test_check_single_property_exists() {
+ final Set<Integer> properties = new ArraySet<>();
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(
+ foldFeatureCommon.hasProperty(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+
+ @Test
+ public void test_check_multiple_properties_exists() {
+ final Set<Integer> properties = new ArraySet<>();
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(foldFeatureCommon.hasProperties(
+ DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+
+ @Test
+ public void test_properties_matches_getter() {
+ final Set<Integer> properties = new ArraySet<>();
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(foldFeatureCommon.getProperties()).isEqualTo(properties);
+ }
+
+ @Test
+ public void test_type_matches_getter() {
+ final Set<Integer> properties = new ArraySet<>();
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(foldFeatureCommon.getType()).isEqualTo(DISPLAY_FOLD_FEATURE_TYPE_HINGE);
+ }
+
+ @Test
+ public void test_create_half_opened_feature() {
+ final CommonFoldingFeature foldingFeature =
+ new CommonFoldingFeature(COMMON_TYPE_HINGE, COMMON_STATE_UNKNOWN, new Rect());
+ final DisplayFoldFeatureCommon foldFeatureCommon = DisplayFoldFeatureCommon.create(
+ foldingFeature, true);
+
+ assertThat(foldFeatureCommon.getType()).isEqualTo(DISPLAY_FOLD_FEATURE_TYPE_HINGE);
+ assertThat(
+ foldFeatureCommon.hasProperty(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+
+ @Test
+ public void test_create_fold_feature_no_half_opened() {
+ final CommonFoldingFeature foldingFeature =
+ new CommonFoldingFeature(COMMON_TYPE_FOLD, COMMON_STATE_UNKNOWN, new Rect());
+ final DisplayFoldFeatureCommon foldFeatureCommon = DisplayFoldFeatureCommon.create(
+ foldingFeature, true);
+
+ assertThat(foldFeatureCommon.getType()).isEqualTo(DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN);
+ assertThat(
+ foldFeatureCommon.hasProperty(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+}
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 82e5aee..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>
@@ -71,6 +72,8 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
+ android:clipChildren="true"
+ android:clipToOutline="true"
android:background="@drawable/bubble_manage_menu_bg"
android:elevation="@dimen/bubble_manage_menu_elevation" />
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/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index 6d63971..434885f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -43,6 +43,7 @@
APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
+ DISABLE_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
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 a9fdea3..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,7 +16,17 @@
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;
import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -26,15 +36,19 @@
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;
import android.database.ContentObserver;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -48,6 +62,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackEvent;
@@ -57,6 +72,9 @@
import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
@@ -66,12 +84,14 @@
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
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 java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -101,6 +121,7 @@
/** Tracks if an uninterruptible animation is in progress */
private boolean mPostCommitAnimationInProgress = false;
+ private boolean mRealCallbackInvoked = false;
/** Tracks if we should start the back gesture on the next motion move event */
private boolean mShouldStartOnNextMoveEvent = false;
@@ -123,6 +144,8 @@
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
private final WindowManager mWindowManager;
+ private final Transitions mTransitions;
+ private final BackTransitionHandler mBackTransitionHandler;
@VisibleForTesting
final Rect mTouchableArea = new Rect();
@@ -190,7 +213,8 @@
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ Transitions transitions) {
this(
shellInit,
shellController,
@@ -201,7 +225,8 @@
context.getContentResolver(),
backAnimationBackground,
shellBackAnimationRegistry,
- shellCommandHandler);
+ shellCommandHandler,
+ transitions);
}
@VisibleForTesting
@@ -215,7 +240,8 @@
ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ Transitions transitions) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -230,6 +256,9 @@
mLatencyTracker = LatencyTracker.getInstance(mContext);
mShellCommandHandler = shellCommandHandler;
mWindowManager = context.getSystemService(WindowManager.class);
+ mTransitions = transitions;
+ mBackTransitionHandler = new BackTransitionHandler();
+ mTransitions.addHandler(mBackTransitionHandler);
updateTouchableArea();
}
@@ -730,7 +759,7 @@
mBackAnimationFinishedCallback = null;
}
- if (mBackNavigationInfo != null) {
+ if (mBackNavigationInfo != null && !mRealCallbackInvoked) {
final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
if (touchTracker.getTriggerBack()) {
dispatchOnBackInvoked(callback);
@@ -738,6 +767,7 @@
tryDispatchOnBackCancelled(callback);
}
}
+ mRealCallbackInvoked = false;
finishBackNavigation(touchTracker.getTriggerBack());
}
@@ -814,15 +844,44 @@
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
// The next callback should be {@link #onBackAnimationFinished}.
+ final boolean migrateBackToTransition = migratePredictiveBackTransition();
if (mCurrentTracker.getTriggerBack()) {
- // notify gesture finished
- mBackNavigationInfo.onBackGestureFinished(true);
+ if (migrateBackToTransition) {
+ // notify core gesture is commit
+ if (shouldTriggerCloseTransition()) {
+ mBackTransitionHandler.mCloseTransitionRequested = true;
+ final IOnBackInvokedCallback callback =
+ mBackNavigationInfo.getOnBackInvokedCallback();
+ // invoked client side onBackInvoked
+ dispatchOnBackInvoked(callback);
+ mRealCallbackInvoked = true;
+ }
+ } else {
+ // notify gesture finished
+ mBackNavigationInfo.onBackGestureFinished(true);
+ }
+
+ // start post animation
dispatchOnBackInvoked(mActiveCallback);
} else {
+ if (migrateBackToTransition
+ && mBackTransitionHandler.mPrepareOpenTransition != null) {
+ mBackTransitionHandler.createClosePrepareTransition();
+ }
tryDispatchOnBackCancelled(mActiveCallback);
}
}
+ // Close window won't create any transition
+ private boolean shouldTriggerCloseTransition() {
+ if (mBackNavigationInfo == null) {
+ return false;
+ }
+ int type = mBackNavigationInfo.getType();
+ return type == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ || type == BackNavigationInfo.TYPE_CROSS_TASK
+ || type == BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+ }
/**
* Called when the post commit animation is completed or timeout.
* This will trigger the real {@link IOnBackInvokedCallback} behavior.
@@ -857,6 +916,7 @@
"mCurrentBackGestureInfo was null when back animation finished");
}
resetTouchTracker();
+ mBackTransitionHandler.onAnimationFinished();
}
/**
@@ -912,6 +972,7 @@
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
mReceivedNullNavigationInfo = false;
+ mBackTransitionHandler.mLastTrigger = triggerBack;
if (mBackNavigationInfo != null) {
mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
@@ -1016,11 +1077,13 @@
endLatencyTracking();
if (!validateAnimationTargets(apps)) {
Log.e(TAG, "Invalid animation targets!");
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
return;
}
mBackAnimationFinishedCallback = finishedCallback;
mApps = apps;
startSystemAnimation();
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
// Dispatch the first progress after animation start for
// smoothing the initial animation, instead of waiting for next
@@ -1041,6 +1104,7 @@
public void onAnimationCancelled() {
mShellExecutor.execute(
() -> {
+ mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
if (!mShellBackAnimationRegistry.cancel(
mBackNavigationInfo != null
? mBackNavigationInfo.getType()
@@ -1073,4 +1137,490 @@
mQueuedTracker.dump(pw, prefix + " ");
}
+ class BackTransitionHandler implements Transitions.TransitionHandler {
+
+ Runnable mOnAnimationFinishCallback;
+ boolean mCloseTransitionRequested;
+ 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 && mClosePrepareTransition == null) {
+ applyFinishOpenTransition();
+ }
+ if (mOnAnimationFinishCallback != null) {
+ mOnAnimationFinishCallback.run();
+ mOnAnimationFinishCallback = null;
+ }
+ }
+
+ void consumeQueuedTransitionIfNeeded() {
+ if (mQueuedTransition != null) {
+ mQueuedTransition.consume();
+ mQueuedTransition = null;
+ }
+ }
+
+ private void applyFinishOpenTransition() {
+ if (mFinishOpenTransaction != null) {
+ mFinishOpenTransaction.apply();
+ mFinishOpenTransaction = null;
+ }
+ if (mFinishOpenTransitionCallback != null) {
+ mFinishOpenTransitionCallback.onTransitionFinished(null);
+ mFinishOpenTransitionCallback = null;
+ }
+ mOpenTransitionInfo = null;
+ mPrepareOpenTransition = null;
+ }
+
+ private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ applyFinishOpenTransition();
+ st.apply();
+ ft.apply();
+ finishCallback.onTransitionFinished(null);
+ mCloseTransitionRequested = false;
+ }
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @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);
+ 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();
+ }
+ }
+ }
+
+ if (handlePrepareTransition(info, st, ft, finishCallback)) {
+ return true;
+ }
+ 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 (mClosePrepareTransition == transition) {
+ mClosePrepareTransition = null;
+ }
+ // try to handle unexpected transition
+ mergePendingTransitions(info);
+
+ if (!isGestureBackTransition(info) || shouldCancelAnimation(info)
+ || !mCloseTransitionRequested) {
+ if (mPrepareOpenTransition != null) {
+ applyFinishOpenTransition();
+ }
+ if (mQueuedTransition != null) {
+ consumeQueuedTransitionIfNeeded();
+ }
+ return;
+ }
+ // Handle the commit transition if this handler is running the open transition.
+ finishCallback.onTransitionFinished(null);
+ if (mCloseTransitionRequested) {
+ if (mApps == null || mApps.length == 0) {
+ if (mQueuedTransition == null) {
+ // animation was done
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
+ } // let queued transition finish.
+ } else {
+ // we are animating, wait until animation finish
+ mOnAnimationFinishCallback = () -> {
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
+ };
+ }
+ }
+ }
+
+ // 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.
+ */
+ private boolean handlePrepareTransition(
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (info.getType() != WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ return false;
+ }
+
+ SurfaceControl openingLeash = null;
+ 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) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (TransitionUtil.isOpeningMode(c.getMode())) {
+ 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;
+ mOpenTransitionInfo = info;
+ return true;
+ }
+
+ private boolean isGestureBackTransition(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (c.hasFlags(FLAG_BACK_GESTURE_ANIMATED)
+ && (TransitionUtil.isOpeningMode(c.getMode())
+ || TransitionUtil.isClosingMode(c.getMode()))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * Check whether this transition is triggered from back gesture commitment.
+ * Reparent the transition targets to animation leashes, so the animation won't be broken.
+ */
+ private boolean handleCloseTransition(@NonNull TransitionInfo info,
+ @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) {
+ if (mApps[i].mode == MODE_OPENING) {
+ openingLeash = mApps[i].leash;
+ }
+ if (mApps[i].mode == MODE_CLOSING) {
+ closingLeash = mApps[i].leash;
+ }
+ }
+ if (openingLeash != null && closingLeash != null) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (TransitionUtil.isOpeningMode(c.getMode())) {
+ final Point offset = c.getEndRelOffset();
+ st.setPosition(c.getLeash(), offset.x, offset.y);
+ st.reparent(c.getLeash(), openingLeash);
+ } else if (TransitionUtil.isClosingMode(c.getMode())) {
+ st.reparent(c.getLeash(), closingLeash);
+ }
+ }
+ }
+ st.apply();
+ // mApps must exists
+ mOnAnimationFinishCallback = () -> {
+ ft.apply();
+ finishCallback.onTransitionFinished(null);
+ mCloseTransitionRequested = false;
+ };
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(
+ @NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ 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) {
+ return new WindowContainerTransaction();
+ }
+ return null;
+ }
+
+ class QueuedTransition {
+ final TransitionInfo mInfo;
+ final SurfaceControl.Transaction mSt;
+ final SurfaceControl.Transaction mFt;
+ final Transitions.TransitionFinishCallback mFinishCallback;
+ QueuedTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction st,
+ @NonNull SurfaceControl.Transaction ft,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ mInfo = info;
+ mSt = st;
+ mFt = ft;
+ mFinishCallback = finishCallback;
+ }
+
+ void consume() {
+ // not animating, consume transition directly
+ if (mApps == null || mApps.length == 0) {
+ applyAndFinish(mSt, mFt, mFinishCallback);
+ return;
+ }
+ // we are animating
+ if (handlePrepareTransition(mInfo, mSt, mFt, mFinishCallback)) {
+ // handle merge transition if any
+ if (mCloseTransitionRequested) {
+ mOnAnimationFinishCallback = () -> {
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
+ };
+ }
+ }
+ handleCloseTransition(mInfo, mSt, mFt, mFinishCallback);
+ }
+ }
+ }
+
+ 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/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 4e0c82b..c7e8df9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -170,7 +170,7 @@
initialTouchPos.set(backMotionEvent.touchX, backMotionEvent.touchY)
transaction.setAnimationTransaction()
- isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
+ isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.isTopActivityLetterboxed
enteringHasSameLetterbox =
isLetterboxed && closingTarget!!.localBounds.equals(enteringTarget!!.localBounds)
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 211fe0d..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,8 @@
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;
import android.util.AttributeSet;
@@ -26,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;
@@ -38,6 +42,7 @@
private ViewGroup mBubbleSectionView;
private ViewGroup mActionsSectionView;
private ImageView mBubbleIconView;
+ private ImageView mBubbleDismissIconView;
private TextView mBubbleTitleView;
public BubbleBarMenuView(Context context) {
@@ -64,6 +69,19 @@
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);
+ mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon);
+ updateThemeColors();
+ }
+
+ private void updateThemeColors() {
+ try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+ 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)));
+ }
}
/** Update menu details with bubble info */
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/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d7d19f7..3fa51a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -468,6 +468,12 @@
if (mImeSourceControl == null || mImeSourceControl.getLeash() == null) {
if (DEBUG) Slog.d(TAG, "No leash available, not starting the animation.");
return;
+ } else if (!mImeRequestedVisible && show) {
+ // we have a control with leash, but the IME was not requested visible before,
+ // therefore aborting the show animation.
+ Slog.e(TAG, "IME was not requested visible, not starting the show animation.");
+ // TODO(b/353463205) fail statsToken here
+ return;
}
}
final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
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/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index bc6ed1f..2e1789a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
@@ -29,13 +28,8 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
-import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
-import android.view.DisplayInfo;
import androidx.annotation.Nullable;
@@ -86,7 +80,7 @@
private final float mFixedRatio;
/** Allows split ratios to calculated dynamically instead of using {@link #mFixedRatio}. */
private final boolean mAllowFlexibleSplitRatios;
- private boolean mIsHorizontalDivision;
+ private final boolean mIsHorizontalDivision;
/** The first target which is still splitting the screen */
private final SnapTarget mFirstSplitTarget;
@@ -98,27 +92,6 @@
private final SnapTarget mDismissEndTarget;
private final SnapTarget mMiddleTarget;
- public static DividerSnapAlgorithm create(Context ctx, Rect insets) {
- DisplayInfo displayInfo = new DisplayInfo();
- ctx.getSystemService(DisplayManager.class).getDisplay(
- Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
- int dividerWindowWidth = ctx.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- int dividerInsets = ctx.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
- return new DividerSnapAlgorithm(ctx.getResources(),
- displayInfo.logicalWidth, displayInfo.logicalHeight,
- dividerWindowWidth - 2 * dividerInsets,
- ctx.getApplicationContext().getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT,
- insets);
- }
-
- public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
- boolean isHorizontalDivision, Rect insets) {
- this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
- DOCKED_INVALID, false /* minimized */, true /* resizable */);
- }
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isHorizontalDivision, Rect insets, int dockSide) {
@@ -160,29 +133,11 @@
}
/**
- * @return whether it's feasible to enable split screen in the current configuration, i.e. when
- * snapping in the middle both tasks are larger than the minimal task size.
- */
- public boolean isSplitScreenFeasible() {
- int statusBarSize = mInsets.top;
- int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
- int size = mIsHorizontalDivision
- ? mDisplayHeight
- : mDisplayWidth;
- int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
- return availableSpace / 2 >= mMinimalSizeResizableTask;
- }
-
- public SnapTarget calculateSnapTarget(int position, float velocity) {
- return calculateSnapTarget(position, velocity, true /* hardDismiss */);
- }
-
- /**
* @param position the top/left position of the divider
* @param velocity current dragging velocity
- * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
+ * @param hardToDismiss if set, make it a bit harder to get reach the dismiss targets
*/
- public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
+ public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardToDismiss) {
if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
return mDismissStartTarget;
}
@@ -190,7 +145,7 @@
return mDismissEndTarget;
}
if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
- return snap(position, hardDismiss);
+ return snap(position, hardToDismiss);
}
if (velocity < 0) {
return mFirstSplitTarget;
@@ -236,19 +191,6 @@
return 0f;
}
- public SnapTarget getClosestDismissTarget(int position) {
- if (position < mFirstSplitTarget.position) {
- return mDismissStartTarget;
- } else if (position > mLastSplitTarget.position) {
- return mDismissEndTarget;
- } else if (position - mDismissStartTarget.position
- < mDismissEndTarget.position - position) {
- return mDismissStartTarget;
- } else {
- return mDismissEndTarget;
- }
- }
-
public SnapTarget getFirstSplitTarget() {
return mFirstSplitTarget;
}
@@ -411,22 +353,6 @@
return mMiddleTarget;
}
- public SnapTarget getNextTarget(SnapTarget snapTarget) {
- int index = mTargets.indexOf(snapTarget);
- if (index != -1 && index < mTargets.size() - 1) {
- return mTargets.get(index + 1);
- }
- return snapTarget;
- }
-
- public SnapTarget getPreviousTarget(SnapTarget snapTarget) {
- int index = mTargets.indexOf(snapTarget);
- if (index != -1 && index > 0) {
- return mTargets.get(index - 1);
- }
- return snapTarget;
- }
-
/**
* @return whether or not there are more than 1 split targets that do not include the two
* dismiss targets, used in deciding to display the middle target for accessibility
@@ -451,26 +377,6 @@
}
/**
- * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
- * if {@param increment} is negative and moves right otherwise.
- */
- public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) {
- int index = mTargets.indexOf(snapTarget);
- if (index != -1) {
- SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment)
- % mTargets.size());
- if (newTarget == mDismissStartTarget) {
- return mLastSplitTarget;
- } else if (newTarget == mDismissEndTarget) {
- return mFirstSplitTarget;
- } else {
- return newTarget;
- }
- }
- return snapTarget;
- }
-
- /**
* Represents a snap target for the divider.
*/
public static class SnapTarget {
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 c02c9cf..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);
}
@@ -238,7 +243,7 @@
public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
- if (taskInfo != null && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
+ if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
}
@@ -256,16 +261,16 @@
// basically cancel all the onboarding flow. We don't have to ignore events in case
// the app is in size compat mode.
if (mIsFirstReachabilityEducationRunning) {
- if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap
- && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) {
+ if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap()
+ && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) {
return;
}
mIsFirstReachabilityEducationRunning = false;
}
- if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
- if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) {
+ if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed()) {
+ if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled()) {
createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
- } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) {
+ } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap()) {
// In this case the app is letterboxed and the letterbox education
// is disabled. In this case we need to understand if it's the first
// time we show the reachability education. When this is happening
@@ -282,7 +287,7 @@
// We activate the first reachability education if the double-tap is enabled.
// If the double tap is not enabled (e.g. thin letterbox) we just set the value
// of the education being seen.
- if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
mIsFirstReachabilityEducationRunning = true;
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
return;
@@ -293,7 +298,7 @@
createOrUpdateCompatLayout(taskInfo, taskListener);
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
- if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
}
// The user aspect ratio button should not be handled when a new TaskInfo is
@@ -305,7 +310,7 @@
}
return;
}
- if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) {
+ if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap()) {
createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
}
}
@@ -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/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 271c07d..8ce7837 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -82,7 +82,7 @@
onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
&& DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
// Don't show the SCM button for freeform tasks
@@ -138,7 +138,7 @@
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
&& DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) {
// Don't show the SCM button for freeform tasks
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 623fead..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,8 +109,9 @@
R.dimen.letterbox_education_dialog_margin);
mDockStateReader = dockStateReader;
mCompatUIConfiguration = compatUIConfiguration;
+ mCompatUIStatusManager = compatUIStatusManager;
mEligibleForLetterboxEducation =
- taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation;
+ taskInfo.appCompatTaskInfo.eligibleForLetterboxEducation();
}
@Override
@@ -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,14 +206,14 @@
@Override
public void release() {
mAnimationController.cancelAnimation();
+ mCompatUIStatusManager.onEducationHidden();
super.release();
}
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
- mEligibleForLetterboxEducation =
- taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation;
+ mEligibleForLetterboxEducation = taskInfo.appCompatTaskInfo.eligibleForLetterboxEducation();
return super.updateCompatInfo(taskInfo, taskListener, canShow);
}
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/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 07082a5..06f2dd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -91,7 +91,7 @@
Function<Integer, Integer> disappearTimeSupplier) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
- mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
+ mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled();
mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
@@ -148,12 +148,12 @@
final int prevTopActivityLetterboxWidth = mTopActivityLetterboxWidth;
final int prevTopActivityLetterboxHeight = mTopActivityLetterboxHeight;
final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
- mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
+ mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled();
mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
mTopActivityLetterboxHeight = appCompatTaskInfo.topActivityLetterboxHeight;
- mHasUserDoubleTapped = appCompatTaskInfo.isFromLetterboxDoubleTap;
+ mHasUserDoubleTapped = appCompatTaskInfo.isFromLetterboxDoubleTap();
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index 8fb4bdb..3f67172 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -238,14 +238,14 @@
// App is not visibly letterboxed if it covers status bar/bottom insets or matches the
// stable bounds, so don't show the button
if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth
- && !taskInfo.isUserFullscreenOverrideEnabled) {
+ && !taskInfo.isUserFullscreenOverrideEnabled()) {
return false;
}
- return taskInfo.topActivityEligibleForUserAspectRatioButton
- && (taskInfo.topActivityBoundsLetterboxed
- || taskInfo.isUserFullscreenOverrideEnabled)
- && !taskInfo.isSystemFullscreenOverrideEnabled
+ return taskInfo.eligibleForUserAspectRatioButton()
+ && (taskInfo.isTopActivityLetterboxed()
+ || taskInfo.isUserFullscreenOverrideEnabled())
+ && !taskInfo.isSystemFullscreenOverrideEnabled()
&& Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt
new file mode 100644
index 0000000..9ee50ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt
@@ -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.wm.shell.compatui.api
+
+import android.util.Log
+
+/**
+ * The component created after a {@link CompatUISpec} definition
+ */
+class CompatUIComponent(
+ private val spec: CompatUISpec,
+ private val id: String
+) {
+
+ /**
+ * Invoked every time a new CompatUIInfo comes from core
+ * @param newInfo The new CompatUIInfo object
+ * @param sharedState The state shared between all the component
+ */
+ fun update(newInfo: CompatUIInfo, state: CompatUIState) {
+ // TODO(b/322817374): To be removed when the implementation is provided.
+ Log.d("CompatUIComponent", "update() newInfo: $newInfo state:$state")
+ }
+
+ fun release() {
+ // TODO(b/322817374): To be removed when the implementation is provided.
+ Log.d("CompatUIComponent", "release()")
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentIdGenerator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentIdGenerator.kt
new file mode 100644
index 0000000..7d663fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentIdGenerator.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.api
+
+/**
+ * Any object responsible to generate an id for a component.
+ */
+interface CompatUIComponentIdGenerator {
+
+ /**
+ * Generates the unique id for a component given a {@link CompatUIInfo} and component
+ * {@link CompatUISpec}.
+ * @param compatUIInfo The object encapsulating information about the current Task.
+ * @param spec The {@link CompatUISpec} for the component.
+ */
+ fun generateId(compatUIInfo: CompatUIInfo, spec: CompatUISpec): String
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt
index 1f04a44..dcaea00 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.activatable
+package com.android.wm.shell.compatui.api
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
-
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
-}
+/**
+ * Abstraction of all the component specific state. Each
+ * component can create its own state implementing this
+ * tagging interface.
+ */
+interface CompatUIComponentState
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISharedState.kt
similarity index 74%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISharedState.kt
index 9a5c77a..33e0d46 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/education/GestureType.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISharedState.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 The Android Open Source Project
+ * 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.shared.education
+package com.android.wm.shell.compatui.api
-enum class GestureType {
- BACK_GESTURE,
-}
+/**
+ * Represents the state shared between all the components.
+ */
+class CompatUISharedState
\ No newline at end of file
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 24c2c8c..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,11 +16,36 @@
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.
+ */
+class CompatUILifecyclePredicates(
+ // Predicate evaluating to true if the component needs to be created
+ val creationPredicate: (CompatUIInfo, CompatUISharedState) -> Boolean,
+ // Predicate evaluating to true if the component needs to be destroyed
+ val removalPredicate: (
+ CompatUIInfo,
+ CompatUISharedState,
+ CompatUIComponentState?
+ ) -> Boolean,
+ // Builder for the initial state of the component
+ val stateBuilder: (
+ CompatUIInfo,
+ CompatUISharedState
+ ) -> CompatUIComponentState? = { _, _ -> null }
+)
+
/**
* Describes each compat ui component to the framework.
*/
-data class CompatUISpec(
+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
-)
\ No newline at end of file
+ val name: String,
+ // The lifecycle definition
+ val lifecycle: CompatUILifecyclePredicates
+)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIState.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIState.kt
new file mode 100644
index 0000000..68307b4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIState.kt
@@ -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.api
+
+/**
+ * Singleton which contains the global state of the compat ui system.
+ */
+class CompatUIState {
+
+ private val components = mutableMapOf<String, CompatUIComponent>()
+
+ val sharedState = CompatUISharedState()
+
+ val componentStates = mutableMapOf<String, CompatUIComponentState>()
+
+ /**
+ * @return The CompatUIComponent for the given componentId if it exists.
+ */
+ fun getUIComponent(componentId: String): CompatUIComponent? =
+ components[componentId]
+
+ /**
+ * Registers a component for a given componentId along with its optional state.
+ * <p/>
+ * @param componentId The identifier for the component to register.
+ * @param comp The {@link CompatUIComponent} instance to register.
+ * @param componentState The optional state specific of the component. Not all components
+ * have a specific state so it can be null.
+ */
+ fun registerUIComponent(
+ componentId: String,
+ comp: CompatUIComponent,
+ componentState: CompatUIComponentState?
+ ) {
+ components[componentId] = comp
+ componentState?.let {
+ componentStates[componentId] = componentState
+ }
+ }
+
+ /**
+ * Unregister a component for a given componentId.
+ * <p/>
+ * @param componentId The identifier for the component to register.
+ */
+ fun unregisterUIComponent(componentId: String) {
+ components.remove(componentId)
+ componentStates.remove(componentId)
+ }
+
+ /**
+ * Get access to the specific {@link CompatUIComponentState} for a {@link CompatUIComponent}
+ * with a given identifier.
+ * <p/>
+ * @param componentId The identifier of the {@link CompatUIComponent}.
+ * @return The optional state for the component of the provided id.
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun <T : CompatUIComponentState> stateForComponent(componentId: String) =
+ componentStates[componentId] as? T
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
index 23205c3..db3fda0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt
@@ -27,9 +27,9 @@
sealed class CompatUIEvents(override val eventId: Int) : CompatUIEvent {
/** Sent when the size compat restart button appears. */
data class SizeCompatRestartButtonAppeared(val taskId: Int) :
- CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_APPEARED)
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_APPEARED)
/** Sent when the size compat restart button is clicked. */
data class SizeCompatRestartButtonClicked(val taskId: Int) :
- CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_CLICKED)
-}
\ No newline at end of file
+ CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_CLICKED)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index 8408ea6..a7d1b42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -16,25 +16,70 @@
package com.android.wm.shell.compatui.impl
+import com.android.wm.shell.compatui.api.CompatUIComponent
+import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator
import com.android.wm.shell.compatui.api.CompatUIEvent
import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.compatui.api.CompatUIInfo
import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUIState
import java.util.function.Consumer
+import java.util.function.IntSupplier
/**
* Default implementation of {@link CompatUIHandler} to handle CompatUI components
*/
class DefaultCompatUIHandler(
- private val compatUIRepository: CompatUIRepository
+ private val compatUIRepository: CompatUIRepository,
+ private val compatUIState: CompatUIState,
+ private val componentIdGenerator: CompatUIComponentIdGenerator
) : CompatUIHandler {
private var compatUIEventSender: Consumer<CompatUIEvent>? = null
+
override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
+ compatUIRepository.iterateOn { spec ->
+ // We get the identifier for the component depending on the task and spec
+ val componentId = componentIdGenerator.generateId(compatUIInfo, spec)
+ // We check in the state if the component already exists
+ var comp = compatUIState.getUIComponent(componentId)
+ if (comp == null) {
+ // We evaluate the predicate
+ if (spec.lifecycle.creationPredicate(compatUIInfo, compatUIState.sharedState)) {
+ // We create the component and store in the
+ // global state
+ comp = CompatUIComponent(spec, componentId)
+ // We initialize the state for the component
+ val compState = spec.lifecycle.stateBuilder(
+ compatUIInfo,
+ compatUIState.sharedState
+ )
+ compatUIState.registerUIComponent(componentId, comp, compState)
+ // Now we can invoke the update passing the shared state and
+ // the state specific to the component
+ comp.update(compatUIInfo, compatUIState)
+ }
+ } else {
+ // The component is present. We check if we need to remove it
+ if (spec.lifecycle.removalPredicate(
+ compatUIInfo,
+ compatUIState.sharedState,
+ compatUIState.stateForComponent(componentId)
+ )) {
+ // We clean the component
+ comp.release()
+ // We remove the component
+ compatUIState.unregisterUIComponent(componentId)
+ } else {
+ // The component exists so we need to invoke the update methods
+ comp.update(compatUIInfo, compatUIState)
+ }
+ }
+ }
// Empty at the moment
}
override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) {
this.compatUIEventSender = compatUIEventSender
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultComponentIdGenerator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultComponentIdGenerator.kt
new file mode 100644
index 0000000..446291b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultComponentIdGenerator.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.impl
+
+import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Default {@link CompatUIComponentIdGenerator} implementation.
+ */
+class DefaultComponentIdGenerator : CompatUIComponentIdGenerator {
+ /**
+ * Simple implementation generating the id from taskId and component name.
+ */
+ override fun generateId(compatUIInfo: CompatUIInfo, spec: CompatUISpec): String =
+ "${compatUIInfo.taskInfo.taskId}-${spec.name}"
+}
\ No newline at end of file
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 2dc6382..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,13 +16,18 @@
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;
import android.app.ActivityTaskManager;
import android.content.Context;
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;
@@ -71,10 +76,14 @@
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;
+import com.android.wm.shell.compatui.api.CompatUIState;
import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
+import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -248,12 +257,16 @@
Lazy<CompatUIConfiguration> compatUIConfiguration,
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
Lazy<AccessibilityManager> accessibilityManager,
- CompatUIRepository compatUIRepository) {
+ CompatUIRepository compatUIRepository,
+ @NonNull CompatUIState compatUIState,
+ @NonNull CompatUIComponentIdGenerator componentIdGenerator,
+ CompatUIStatusManager compatUIStatusManager) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
if (Flags.appCompatUiFramework()) {
- return Optional.of(new DefaultCompatUIHandler(compatUIRepository));
+ return Optional.of(new DefaultCompatUIHandler(compatUIRepository, compatUIState,
+ componentIdGenerator));
}
return Optional.of(
new CompatUIController(
@@ -269,7 +282,34 @@
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
+ @Provides
+ static CompatUIState provideCompatUIState() {
+ return new CompatUIState();
+ }
+
+ @WMSingleton
+ @Provides
+ static CompatUIComponentIdGenerator provideCompatUIComponentIdGenerator() {
+ return new DefaultComponentIdGenerator();
}
@WMSingleton
@@ -375,7 +415,8 @@
@ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ Transitions transitions) {
if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
@@ -387,7 +428,8 @@
context,
backAnimationBackground,
animations,
- shellCommandHandler));
+ shellCommandHandler,
+ transitions));
}
return Optional.empty();
}
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 9cfbde4..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;
@@ -67,6 +69,8 @@
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
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;
@@ -538,6 +542,7 @@
DragAndDropController dragAndDropController,
Transitions transitions,
KeyguardManager keyguardManager,
+ ReturnToDragStartAnimator returnToDragStartAnimator,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@@ -553,13 +558,13 @@
InteractionJankMonitor interactionJankMonitor) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler,
+ dragAndDropController, transitions, keyguardManager,
+ returnToDragStartAnimator, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
- recentsTransitionHandler, multiInstanceHelper,
- mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null),
- interactionJankMonitor);
+ recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
+ recentTasksController.orElse(null), interactionJankMonitor);
}
@WMSingleton
@@ -587,6 +592,13 @@
);
}
+ @WMSingleton
+ @Provides
+ static ReturnToDragStartAnimator provideReturnToDragStartAnimator(
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ReturnToDragStartAnimator(interactionJankMonitor);
+ }
+
@WMSingleton
@Provides
@@ -594,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 9fcf73d..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,12 +68,17 @@
ORIENTATION_LANDSCAPE -> {
if (taskInfo.isResizeable) {
if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
- // Respect apps fullscreen width
- Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+ // 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)
}
}
@@ -76,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.topActivityLetterboxHeight
+ 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)
}
}
@@ -143,9 +160,9 @@
/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
- if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
- val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
- val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+ val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth
+ val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
+ if (taskInfo.appCompatTaskInfo.isTopActivityLetterboxed) {
return maxOf(appLetterboxWidth, appLetterboxHeight) /
minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
}
@@ -199,7 +216,7 @@
}
// Then check if the activity is portrait when letterboxed
- appCompatTaskInfo.topActivityBoundsLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+ appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
// Then check if the activity is portrait
appBounds != null -> appBounds.height() > appBounds.width()
@@ -208,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 2bb172f..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
@@ -66,7 +66,6 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
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
@@ -79,13 +78,14 @@
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
+import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE
import com.android.wm.shell.sysui.ShellCommandHandler
@@ -96,6 +96,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import com.android.wm.shell.windowdecor.extension.isFullscreen
import com.android.wm.shell.windowdecor.extension.isMultiWindow
@@ -117,6 +118,7 @@
private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
private val keyguardManager: KeyguardManager,
+ private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
@@ -161,8 +163,10 @@
}
private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
- visualIndicator?.releaseVisualIndicator(tx)
- visualIndicator = null
+ visualIndicator?.fadeOutIndicator {
+ visualIndicator?.releaseVisualIndicator(tx)
+ visualIndicator = null
+ }
}
}
@@ -191,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) {
@@ -211,7 +215,11 @@
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
- dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
+ dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener
+ }
+
+ fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
+ returnToDragStartAnimator.setTaskRepositionAnimationListener(listener)
}
/** Setter needed to avoid cyclic dependency. */
@@ -639,21 +647,50 @@
/**
* Quick-resize to the right or left half of the stable bounds.
*
+ * @param taskInfo current task that is being snap-resized via dragging or maximize menu button
+ * @param currentDragBounds current position of the task leash being dragged (or current task
+ * bounds if being snapped resize via maximize menu button)
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
- fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
+ fun snapToHalfScreen(
+ taskInfo: RunningTaskInfo,
+ currentDragBounds: Rect,
+ position: SnapPosition
+ ) {
val destinationBounds = getSnapBounds(taskInfo, position)
if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
+ @VisibleForTesting
+ fun handleSnapResizingTask(
+ taskInfo: RunningTaskInfo,
+ position: SnapPosition,
+ taskSurface: SurfaceControl,
+ currentDragBounds: Rect,
+ dragStartBounds: Rect
+ ) {
+ releaseVisualIndicator()
+ if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
+ // reposition non-resizable app back to its original position before being dragged
+ returnToDragStartAnimator.start(
+ taskInfo.taskId,
+ taskSurface,
+ startBounds = currentDragBounds,
+ endBounds = dragStartBounds
+ )
+ } else {
+ snapToHalfScreen(taskInfo, currentDragBounds, position)
+ }
+ }
+
private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
@@ -819,8 +856,12 @@
// Check if we should skip handling this transition
var reason = ""
val triggerTask = request.triggerTask
+ var shouldHandleMidRecentsFreeformLaunch =
+ recentsAnimationRunning && isFreeformRelaunch(triggerTask, request)
val shouldHandleRequest =
when {
+ // Handle freeform relaunch during recents animation
+ shouldHandleMidRecentsFreeformLaunch -> true
recentsAnimationRunning -> {
reason = "recents animation is running"
false
@@ -860,6 +901,8 @@
val result =
triggerTask?.let { task ->
when {
+ // Check if freeform task launch during recents should be handled
+ shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
// Check if the closing task needs to be handled
TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
// Check if the top task shouldn't be allowed to enter desktop mode
@@ -893,6 +936,12 @@
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
+ /** Returns whether an existing desktop task is being relaunched in freeform or not. */
+ private fun isFreeformRelaunch(triggerTask: RunningTaskInfo?, request: TransitionRequestInfo) =
+ (triggerTask != null && triggerTask.windowingMode == WINDOWING_MODE_FREEFORM
+ && TransitionUtil.isOpeningType(request.type)
+ && taskRepository.isActiveTask(triggerTask.taskId))
+
private fun isIncompatibleTask(task: TaskInfo) =
DesktopModeFlags.MODALS_POLICY.isEnabled(context)
&& isTopActivityExemptFromDesktopWindowing(context, task)
@@ -930,9 +979,8 @@
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = newTaskWindowingMode
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val launchIntent = PendingIntent.getActivity(
context,
@@ -957,6 +1005,21 @@
}
}
+ /**
+ * Handles the case where a freeform task is launched from recents.
+ *
+ * This is a special case where we want to launch the task in fullscreen instead of freeform.
+ */
+ private fun handleMidRecentsFreeformTaskLaunch(
+ task: RunningTaskInfo
+ ): WindowContainerTransaction? {
+ logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch")
+ val wct = WindowContainerTransaction()
+ addMoveToFullscreenChanges(wct, task)
+ wct.reorder(task.token, true)
+ return wct
+ }
+
private fun handleFreeformTaskLaunch(
task: RunningTaskInfo,
transition: IBinder
@@ -1007,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)
@@ -1239,7 +1307,7 @@
taskSurface: SurfaceControl,
inputX: Float,
taskTop: Float
- ): DesktopModeVisualIndicator.IndicatorType {
+ ): IndicatorType {
// If the visual indicator does not exist, create it.
val indicator =
visualIndicator
@@ -1260,16 +1328,22 @@
* that change. Otherwise, ensure bounds are up to date.
*
* @param taskInfo the task being dragged.
+ * @param taskSurface the leash of the task being dragged.
* @param position position of surface when drag ends.
* @param inputCoordinate the coordinates of the motion event
- * @param taskBounds the updated bounds of the task being dragged.
+ * @param currentDragBounds the current bounds of where the visible task is (might be actual
+ * task bounds or just task leash)
+ * @param validDragArea the bounds of where the task can be dragged within the display.
+ * @param dragStartBounds the bounds of the task before starting dragging.
*/
fun onDragPositioningEnd(
taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect,
- validDragArea: Rect
+ currentDragBounds: Rect,
+ validDragArea: Rect,
+ dragStartBounds: Rect,
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -1278,41 +1352,44 @@
val indicator = visualIndicator ?: return
val indicatorType =
indicator.updateIndicatorType(
- PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+ PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
taskInfo.windowingMode
)
when (indicatorType) {
- DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ IndicatorType.TO_FULLSCREEN_INDICATOR -> {
moveToFullscreenWithAnimation(
taskInfo,
position,
DesktopModeTransitionSource.TASK_DRAG
)
}
- DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
- releaseVisualIndicator()
- snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+ IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ handleSnapResizingTask(
+ taskInfo, SnapPosition.LEFT, taskSurface, currentDragBounds, dragStartBounds
+ )
}
- DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
- releaseVisualIndicator()
- snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
+ IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ handleSnapResizingTask(
+ taskInfo, SnapPosition.RIGHT, taskSurface, currentDragBounds, dragStartBounds
+ )
}
- DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
- // If task bounds are outside valid drag area, snap them inward and perform a
- // transaction to set bounds.
- if (
- DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
- taskBounds,
- validDragArea
- )
- ) {
- val wct = WindowContainerTransaction()
- wct.setBounds(taskInfo.token, taskBounds)
- transitions.startTransition(TRANSIT_CHANGE, wct, null)
- }
+ IndicatorType.NO_INDICATOR -> {
+ // If task bounds are outside valid drag area, snap them inward
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ currentDragBounds,
+ validDragArea
+ )
+
+ if (currentDragBounds == dragStartBounds) return
+
+ // Update task bounds so that the task position will match the position of its leash
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, currentDragBounds)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null)
+
releaseVisualIndicator()
}
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ IndicatorType.TO_DESKTOP_INDICATOR -> {
throw IllegalArgumentException(
"Should not be receiving TO_DESKTOP_INDICATOR for " + "a freeform 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 9e79eddb0..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 com.android.internal.protolog.ProtoLog
+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
@@ -220,16 +211,18 @@
startCancelAnimation()
} else if (
state.draggedTaskChange != null &&
- (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
+ (cancelState == CancelState.CANCEL_SPLIT_LEFT ||
cancelState == CancelState.CANCEL_SPLIT_RIGHT)
- ) {
+ ) {
// We have a valid dragged task, but the animation will be handled by
// SplitScreenController; request the transition here.
- @SplitPosition val splitPosition = if (cancelState == CancelState.CANCEL_SPLIT_LEFT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
+ @SplitPosition
+ val splitPosition =
+ if (cancelState == CancelState.CANCEL_SPLIT_LEFT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
val wct = WindowContainerTransaction()
restoreWindowOrder(wct, state)
state.startTransitionFinishTransaction?.apply()
@@ -252,20 +245,20 @@
wct: WindowContainerTransaction
) {
val state = requireTransitionState()
- val taskInfo = state.draggedTaskChange?.taskInfo
- ?: error("Expected non-null taskInfo")
+ val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
val taskBounds = Rect(taskInfo.configuration.windowConfiguration.bounds)
val taskScale = state.dragAnimator.scale
val scaledWidth = taskBounds.width() * taskScale
val scaledHeight = taskBounds.height() * taskScale
val dragPosition = PointF(state.dragAnimator.position)
state.dragAnimator.cancelAnimator()
- val animatedTaskBounds = Rect(
- dragPosition.x.toInt(),
- dragPosition.y.toInt(),
- (dragPosition.x + scaledWidth).toInt(),
- (dragPosition.y + scaledHeight).toInt()
- )
+ val animatedTaskBounds =
+ Rect(
+ dragPosition.x.toInt(),
+ dragPosition.y.toInt(),
+ (dragPosition.x + scaledWidth).toInt(),
+ (dragPosition.y + scaledHeight).toInt()
+ )
requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
}
@@ -286,12 +279,7 @@
}
wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
wct.setDensityDpi(taskInfo.token, context.resources.displayMetrics.densityDpi)
- splitScreenController.requestEnterSplitSelect(
- taskInfo,
- wct,
- splitPosition,
- taskBounds
- )
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds)
}
override fun startAnimation(
@@ -310,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)
@@ -341,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)
@@ -360,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)
}
@@ -373,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)
}
@@ -390,7 +372,7 @@
// occurred.
if (
change.taskInfo?.taskId == state.draggedTaskId &&
- state.cancelState != CancelState.STANDARD_CANCEL
+ state.cancelState != CancelState.STANDARD_CANCEL
) {
// We need access to the dragged task's change in both non-cancel and split
// cancel cases.
@@ -398,8 +380,8 @@
}
if (
change.taskInfo?.taskId == state.draggedTaskId &&
- state.cancelState == CancelState.NO_CANCEL
- ) {
+ state.cancelState == CancelState.NO_CANCEL
+ ) {
taskDisplayAreaOrganizer.reparentToDisplayArea(
change.endDisplayId,
change.leash,
@@ -407,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)
}
@@ -432,19 +414,20 @@
startCancelDragToDesktopTransition()
} else if (
state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
- state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
- ){
+ state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
+ ) {
// Cancel-early case for split-cancel. The state was flagged already as a cancel for
// requesting split select. Similar to the above, this can happen due to quick fling
// gestures. We can simply request split here without needing to calculate animated
// task bounds as the task has not shrunk at all.
- val splitPosition = if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) {
- SPLIT_POSITION_TOP_OR_LEFT
- } else {
- SPLIT_POSITION_BOTTOM_OR_RIGHT
- }
- val taskInfo = state.draggedTaskChange?.taskInfo
- ?: error("Expected non-null task info.")
+ val splitPosition =
+ if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) {
+ SPLIT_POSITION_TOP_OR_LEFT
+ } else {
+ SPLIT_POSITION_BOTTOM_OR_RIGHT
+ }
+ val taskInfo =
+ state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
val wct = WindowContainerTransaction()
restoreWindowOrder(wct)
state.startTransitionFinishTransaction?.apply()
@@ -454,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,
@@ -463,8 +455,10 @@
) {
val state = requireTransitionState()
// We don't want to merge the split select animation if that's what we requested.
- if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
- state.cancelState == CancelState.CANCEL_SPLIT_RIGHT) {
+ if (
+ state.cancelState == CancelState.CANCEL_SPLIT_LEFT ||
+ state.cancelState == CancelState.CANCEL_SPLIT_RIGHT
+ ) {
clearState()
return
}
@@ -483,103 +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)
@@ -592,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
@@ -673,9 +694,7 @@
val wct = WindowContainerTransaction()
restoreWindowOrder(wct, state)
state.cancelTransitionToken =
- transitions.startTransition(
- TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this
- )
+ transitions.startTransition(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this)
}
private fun restoreWindowOrder(
@@ -708,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
}
@@ -732,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)
@@ -749,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
@@ -761,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()
@@ -775,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,
@@ -798,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/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
new file mode 100644
index 0000000..be67a40
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.desktopmode
+
+import android.animation.Animator
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.view.SurfaceControl
+import androidx.core.animation.addListener
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
+import java.util.function.Supplier
+
+/** Animates the task surface moving from its current drag position to its pre-drag position. */
+class ReturnToDragStartAnimator(
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val interactionJankMonitor: InteractionJankMonitor
+) {
+ private var boundsAnimator: Animator? = null
+ private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener
+
+ constructor(interactionJankMonitor: InteractionJankMonitor) :
+ this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
+
+ /** Sets a listener for the start and end of the reposition animation. */
+ fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
+ taskRepositionAnimationListener = listener
+ }
+
+ /** Builds new animator and starts animation of task leash reposition. */
+ fun start(taskId: Int, taskSurface: SurfaceControl, startBounds: Rect, endBounds: Rect) {
+ val tx = transactionSupplier.get()
+
+ boundsAnimator?.cancel()
+ boundsAnimator =
+ ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds)
+ .setDuration(RETURN_TO_DRAG_START_ANIMATION_MS)
+ .apply {
+ addListener(
+ onStart = {
+ val startTransaction = transactionSupplier.get()
+ startTransaction
+ .setPosition(
+ taskSurface,
+ startBounds.left.toFloat(),
+ startBounds.top.toFloat()
+ )
+ .show(taskSurface)
+ .apply()
+ taskRepositionAnimationListener.onAnimationStart(taskId)
+ },
+ onEnd = {
+ val finishTransaction = transactionSupplier.get()
+ finishTransaction
+ .setPosition(
+ taskSurface,
+ endBounds.left.toFloat(),
+ endBounds.top.toFloat()
+ )
+ .show(taskSurface)
+ .apply()
+ taskRepositionAnimationListener.onAnimationEnd(taskId)
+ boundsAnimator = null
+ // TODO(b/354658237) - show toast with relevant string
+ // TODO(b/339582583) - add Jank CUJ using interactionJankMonitor
+ }
+ )
+ addUpdateListener { anim ->
+ val rect = anim.animatedValue as Rect
+ tx.setPosition(taskSurface, rect.left.toFloat(), rect.top.toFloat())
+ .show(taskSurface)
+ .apply()
+ }
+ }
+ .also(ValueAnimator::start)
+ }
+
+ companion object {
+ const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index c35d77a..bf185a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -45,15 +45,24 @@
private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
private var boundsAnimator: Animator? = null
+ private var initialBounds: Rect? = null
constructor(
transitions: Transitions,
interactionJankMonitor: InteractionJankMonitor
) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
- /** Starts a quick resize transition. */
- fun startTransition(wct: WindowContainerTransaction) {
+ /**
+ * Starts a quick resize transition.
+ *
+ * @param wct WindowContainerTransaction that will update core about the task changes applied
+ * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the
+ * bounds of the actual task). This is provided so that the animation
+ * resizing can begin where the task leash currently is for smoother UX.
+ */
+ fun startTransition(wct: WindowContainerTransaction, taskLeashBounds: Rect? = null) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
+ initialBounds = taskLeashBounds
}
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
@@ -70,7 +79,7 @@
val change = findRelevantChange(info)
val leash = change.leash
val taskId = checkNotNull(change.taskInfo).taskId
- val startBounds = change.startAbsBounds
+ val startBounds = initialBounds ?: change.startAbsBounds
val endBounds = change.endAbsBounds
val tx = transactionSupplier.get()
@@ -92,7 +101,7 @@
onTaskResizeAnimationListener.onAnimationStart(
taskId,
startTransaction,
- startBounds
+ startBounds,
)
},
onEnd = {
@@ -106,6 +115,7 @@
.show(leash)
onTaskResizeAnimationListener.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null)
+ initialBounds = null
boundsAnimator = null
interactionJankMonitor.end(
Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
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/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 333c75f..abec3b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -170,7 +170,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
- if (!handles(info) || mIsLaunchingActivityOverLockscreen) {
+ if (!handles(info)) {
return false;
}
@@ -185,6 +185,9 @@
transition, info, startTransaction, finishTransaction, finishCallback);
}
+ if (mIsLaunchingActivityOverLockscreen) {
+ return false;
+ }
// Occlude/unocclude animations are only played if the keyguard is locked.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
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 94fe286..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);
}
@@ -300,6 +331,10 @@
int launcherRotation, Rect hotseatKeepClearArea) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"getSwipePipToHomeBounds: %s", componentName);
+ // preemptively add the keep clear area for Hotseat, so that it is taken into account
+ // when calculating the entry destination bounds of PiP window
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea);
mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
mPipBoundsAlgorithm);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
@@ -328,6 +363,23 @@
mPipRecentsAnimationListener.onPipAnimationStarted();
}
+ private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
+ mPipDisplayLayoutState.getDisplayBounds().right,
+ mPipDisplayLayoutState.getDisplayBounds().bottom);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
+ } else {
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
+ }
+ mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+ }
+
@Override
public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
@@ -349,10 +401,14 @@
if (mPipTransitionState.isInSwipePipToHomeTransition()) {
mPipTransitionState.resetSwipePipToHomeState();
}
- mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ if (mOnIsInPipStateChangedListener != null) {
+ mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ }
break;
case PipTransitionState.EXITED_PIP:
- mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ if (mOnIsInPipStateChangedListener != null) {
+ mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ }
break;
}
}
@@ -499,7 +555,10 @@
public void setShelfHeight(boolean visible, int height) {}
@Override
- public void setLauncherKeepClearAreaHeight(boolean visible, int height) {}
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
+ (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height));
+ }
@Override
public void setLauncherAppIconSize(int iconSizePx) {}
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 f387e72..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
@@ -38,6 +38,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.Size;
import android.view.DisplayCutout;
@@ -78,6 +79,8 @@
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
+ private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
+ SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
// Allow PIP to resize to a slightly bigger state upon touch
private boolean mEnableResize;
@@ -134,6 +137,10 @@
// Temp vars
private final Rect mTmpBounds = new Rect();
+ // Callbacks
+ private final Runnable mMoveOnShelVisibilityChanged;
+
+
/**
* A listener for the PIP menu activity.
*/
@@ -217,6 +224,26 @@
mPipPerfHintController);
mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
+ mMoveOnShelVisibilityChanged = () -> {
+ if (mIsImeShowing && mImeHeight > mShelfHeight) {
+ // Early bail-out if IME is visible with a larger height present;
+ // this should block unnecessary PiP movement since we delay checking for
+ // KCA triggered movement to wait for other transitions (e.g. due to IME changes).
+ return;
+ }
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+ || mPipBoundsState.hasUserResizedPip());
+ int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
+ - mPipBoundsState.getBounds().top;
+
+ if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
+ // If the user hasn't interacted with PiP, we respect the keep clear areas
+ mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+ }
+ });
+ };
+
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
@@ -356,9 +383,14 @@
mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
int delta = mPipBoundsState.getMovementBounds().bottom
- mPipBoundsState.getBounds().top;
-
boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
|| mPipBoundsState.hasUserResizedPip());
+
+ if (!imeVisible && !hasUserInteracted) {
+ delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
+ - mPipBoundsState.getBounds().top;
+ }
+
if ((imeVisible && delta < 0) || (!imeVisible && !hasUserInteracted)) {
// The policy is to ignore an IME disappearing if user has interacted with PiP.
// Otherwise, only offset due to an appearing IME if PiP occludes it.
@@ -370,6 +402,16 @@
void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
mIsShelfShowing = shelfVisible;
mShelfHeight = shelfHeight;
+
+ // We need to remove the callback even if the shelf is visible, in case it the delayed
+ // callback hasn't been executed yet to avoid the wrong final state.
+ mMainExecutor.removeCallbacks(mMoveOnShelVisibilityChanged);
+ if (shelfVisible) {
+ mMoveOnShelVisibilityChanged.run();
+ } else {
+ // Postpone moving in response to hide of Launcher in case there's another change
+ mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY);
+ }
}
/**
@@ -1039,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/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 33703ad..7790c51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -108,10 +108,6 @@
//
@Nullable
- private WindowContainerToken mPipTaskToken;
- @Nullable
- private SurfaceControl mPipLeash;
- @Nullable
private Transitions.TransitionFinishCallback mFinishCallback;
public PipTransition(
@@ -402,7 +398,6 @@
finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
animator.setAnimationEndCallback(() -> {
- mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
});
@@ -444,15 +439,16 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- TransitionInfo.Change pipChange = getPipChange(info);
+ WindowContainerToken pipToken = mPipTransitionState.mPipTaskToken;
+
+ TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
if (pipChange == null) {
return false;
}
Rect startBounds = pipChange.getStartAbsBounds();
Rect endBounds = pipChange.getEndAbsBounds();
- SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
- Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
+ SurfaceControl pipLeash = pipChange.getLeash();
PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
startTransaction, startBounds, startBounds, endBounds,
@@ -491,6 +487,18 @@
return null;
}
+ @Nullable
+ private TransitionInfo.Change getChangeByToken(TransitionInfo info,
+ WindowContainerToken token) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getToken().equals(token)) {
+ return change;
+ }
+ }
+ return null;
+ }
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
// cache the original task token to check for multi-activity case later
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/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 234b4d0..ad3f4f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -19,12 +19,14 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
@@ -775,6 +777,20 @@
// Don't consider order-only & non-leaf changes as changing apps.
if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
+ // Check if the changing app is moving to top and fullscreen. This handles
+ // the case where we moved from desktop to recents and launching a desktop
+ // task in fullscreen.
+ if ((change.getFlags() & FLAG_MOVED_TO_TOP) != 0
+ && taskInfo != null
+ && taskInfo.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN) {
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ openingTaskIsLeafs = new IntArray();
+ }
+ openingTasks.add(change);
+ openingTaskIsLeafs.add(1);
+ }
} else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
&& !isRecentsTask ) {
// Unless it is a 3p launcher. This means that the 3p launcher was already
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 0ca244c..59aa792 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -109,35 +109,6 @@
in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
- * Version of startTasks using legacy transition system.
- */
- oneway void startTasksWithLegacyTransition(int taskId1, in Bundle options1, int taskId2,
- in Bundle options2, int splitPosition, int snapPosition,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 11;
-
- /**
- * Starts a pair of intent and task using legacy transition system.
- */
- oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, int userId1,
- in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
-
- /**
- * Starts a pair of shortcut and task using legacy transition system.
- */
- oneway void startShortcutAndTaskWithLegacyTransition(in ShortcutInfo shortcutInfo,
- in Bundle options1, int taskId, in Bundle options2, int splitPosition, int snapPosition,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
-
- /**
- * Start a pair of intents using legacy transition system.
- */
- oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1, int userId1,
- in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
- int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
- int snapPosition, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
-
- /**
* Start a pair of intents in one transition.
*/
oneway void startIntents(in PendingIntent pendingIntent1, int userId1,
@@ -146,20 +117,6 @@
int snapPosition, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
- * Blocking call that notifies and gets additional split-screen targets when entering
- * recents (for example: the dividerBar).
- * @param appTargets apps that will be re-parented to display area
- */
- RemoteAnimationTarget[] onGoingToRecentsLegacy(in RemoteAnimationTarget[] appTargets) = 13;
-
- /**
- * Blocking call that notifies and gets additional split-screen targets when entering
- * recents (for example: the dividerBar). Different than the method above in that this one
- * does not expect split to currently be running.
- */
- RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
-
- /**
* Reverse the split.
*/
oneway void switchSplitPosition() = 22;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c4af148..83f827a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -23,7 +23,6 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.MultiInstanceHelper.getComponent;
import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent;
@@ -35,9 +34,9 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -201,11 +200,6 @@
@VisibleForTesting
StageCoordinator mStageCoordinator;
- // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
- // outside the bounds of the roots by being reparented into a higher level fullscreen container
- private SurfaceControl mGoingToRecentsTasksLayer;
- private SurfaceControl mStartingSplitTasksLayer;
-
/**
* @param stageCoordinator if null, a stage coordinator will be created when this controller is
* initialized. Can be non-null for testing purposes.
@@ -457,11 +451,7 @@
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
- if (ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
- } else {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
- }
+ mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
}
@Override
@@ -606,7 +596,8 @@
*/
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
- mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startShortcut: reason=%d", ENTER_REASON_LAUNCHER);
+ mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
startShortcut(packageName, shortcutId, position, options, user);
}
@@ -640,37 +631,6 @@
activityOptions.toBundle(), user);
}
- void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- if (options1 == null) options1 = new Bundle();
- final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
-
- final String packageName1 = shortcutInfo.getPackage();
- final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
- final int userId1 = shortcutInfo.getUserId();
- final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
- if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
- activityOptions.setApplyMultipleTaskFlagForShortcut(true);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
- } else {
- taskId = INVALID_TASK_ID;
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Cancel entering split as not supporting multi-instances");
- Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
- "app package " + packageName1 + " does not support multi-instance"));
- Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
- Toast.LENGTH_SHORT).show();
- }
- }
-
- mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
- activityOptions.toBundle(), taskId, options2, splitPosition, snapPosition, adapter,
- instanceId);
- }
-
void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
@@ -711,37 +671,12 @@
public void startIntentWithInstanceId(PendingIntent intent, int userId,
@Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options,
@NonNull InstanceId instanceId) {
- mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntentWithInstanceId: reason=%d",
+ ENTER_REASON_LAUNCHER);
+ mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */);
}
- private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- Intent fillInIntent = null;
- final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
- final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
- final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
- if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) {
- fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
- } else {
- taskId = INVALID_TASK_ID;
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Cancel entering split as not supporting multi-instances");
- Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
- "app package " + packageName1 + " does not support multi-instance"));
- Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
- Toast.LENGTH_SHORT).show();
- }
- }
- mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
- options1, taskId, options2, splitPosition, snapPosition, adapter, instanceId);
- }
-
private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@@ -778,38 +713,6 @@
options2, splitPosition, snapPosition, remoteTransition, instanceId);
}
- private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
- @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- Intent fillInIntent1 = null;
- Intent fillInIntent2 = null;
- final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
- final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
- if (samePackage(packageName1, packageName2, userId1, userId2)) {
- if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
- fillInIntent1 = new Intent();
- fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- fillInIntent2 = new Intent();
- fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
- } else {
- pendingIntent2 = null;
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Cancel entering split as not supporting multi-instances");
- Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
- "app package " + packageName1 + " does not support multi-instance"));
- Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
- Toast.LENGTH_SHORT).show();
- }
- }
- mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
- shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
- splitPosition, snapPosition, adapter, instanceId);
- }
-
private void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@@ -891,11 +794,8 @@
if (taskInfo != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Found suitable background task=%s", taskInfo);
- if (ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
- } else {
- startTask(taskInfo.taskId, position, options, hideTaskToken);
- }
+ mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
+
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
}
@@ -995,63 +895,6 @@
return fillInIntent2;
}
- RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
- if (ENABLE_SHELL_TRANSITIONS) return null;
-
- if (isSplitScreenVisible()) {
- // Evict child tasks except the top visible one under split root to ensure it could be
- // launched as full screen when switching to it on recents.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictInvisibleChildTasks(wct);
- mSyncQueue.queue(wct);
- } else {
- return null;
- }
-
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- if (mGoingToRecentsTasksLayer != null) {
- t.remove(mGoingToRecentsTasksLayer);
- }
- mGoingToRecentsTasksLayer = reparentSplitTasksForAnimation(apps, t,
- "SplitScreenController#onGoingToRecentsLegacy" /* callsite */);
- t.apply();
- mTransactionPool.release(t);
-
- return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
- }
-
- RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
- if (ENABLE_SHELL_TRANSITIONS) return null;
-
- int openingApps = 0;
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) openingApps++;
- }
- if (openingApps < 2) {
- // Not having enough apps to enter split screen
- return null;
- }
-
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- if (mStartingSplitTasksLayer != null) {
- t.remove(mStartingSplitTasksLayer);
- }
- mStartingSplitTasksLayer = reparentSplitTasksForAnimation(apps, t,
- "SplitScreenController#onStartingSplitLegacy" /* callsite */);
- t.apply();
- mTransactionPool.release(t);
-
- try {
- return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};
- } finally {
- for (RemoteAnimationTarget appTarget : apps) {
- if (appTarget.leash != null) {
- appTarget.leash.release();
- }
- }
- }
- }
-
private SurfaceControl reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps,
SurfaceControl.Transaction t, String callsite) {
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
@@ -1348,41 +1191,6 @@
}
@Override
- public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
- int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- taskId1, options1, taskId2, options2, splitPosition, snapPosition,
- adapter, instanceId));
- }
-
- @Override
- public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
- Bundle options1, int taskId, Bundle options2, int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- executeRemoteCallWithTaskPermission(mController,
- "startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
- userId1, options1, taskId, options2, splitPosition,
- snapPosition, adapter, instanceId));
- }
-
- @Override
- public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- executeRemoteCallWithTaskPermission(mController,
- "startShortcutAndTaskWithLegacyTransition", (controller) ->
- controller.startShortcutAndTaskWithLegacyTransition(
- shortcutInfo, options1, taskId, options2, splitPosition,
- snapPosition, adapter, instanceId));
- }
-
- @Override
public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
@Nullable Bundle options2, @SplitPosition int splitPosition,
@PersistentSnapPosition int snapPosition,
@@ -1415,21 +1223,6 @@
}
@Override
- public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
- @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
- @Nullable Bundle options2, @SplitPosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
- (controller) ->
- controller.startIntentsWithLegacyTransition(pendingIntent1, userId1,
- shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2,
- options2, splitPosition, snapPosition, adapter, instanceId)
- );
- }
-
- @Override
public void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@@ -1461,24 +1254,6 @@
}
@Override
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
- final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
- executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
- (controller) -> out[0] = controller.onGoingToRecentsLegacy(apps),
- true /* blocking */);
- return out[0];
- }
-
- @Override
- public RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
- final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
- executeRemoteCallWithTaskPermission(mController, "onStartingSplitLegacy",
- (controller) -> out[0] = controller.onStartingSplitLegacy(apps),
- true /* blocking */);
- return out[0];
- }
-
- @Override
public void switchSplitPosition() {
executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
(controller) -> controller.switchSplitPosition("remoteCall"));
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 2531ff1..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
@@ -24,8 +24,6 @@
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -59,14 +57,12 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -81,7 +77,6 @@
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -121,7 +116,6 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.policy.FoldLockSettingsObserver;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -129,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;
@@ -143,9 +137,7 @@
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
-import com.android.wm.shell.splitscreen.SplitScreenController.SplitEnterReason;
import com.android.wm.shell.transition.DefaultMixedHandler;
-import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.SplitBounds;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -227,7 +219,7 @@
// Tracks whether we should update the recent tasks. Only allow this to happen in between enter
// and exit, since exit itself can trigger a number of changes that update the stages.
- private boolean mShouldUpdateRecents;
+ private boolean mShouldUpdateRecents = true;
private boolean mExitSplitScreenOnHide;
private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
@@ -368,11 +360,7 @@
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
- // With shell transition, we should update recents tile each callback so set this to true by
- // default.
- mShouldUpdateRecents = ENABLE_SHELL_TRANSITIONS;
- mFoldLockSettingsObserver =
- new FoldLockSettingsObserver(mainHandler, context);
+ mFoldLockSettingsObserver = new FoldLockSettingsObserver(mainHandler, context);
mFoldLockSettingsObserver.register();
}
@@ -491,18 +479,12 @@
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId,
stagePosition);
prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
- if (ENABLE_SHELL_TRANSITIONS) {
- mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
- null, this,
- isSplitScreenVisible()
- ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
- !mIsDropEntering);
- } else {
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- });
- }
+ mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
+ null, this,
+ isSplitScreenVisible()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
+ !mIsDropEntering);
+
// Due to drag already pip task entering split by this method so need to reset flag here.
mIsDropEntering = false;
mSkipEvictingMainStageChildren = false;
@@ -653,10 +635,6 @@
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
- if (!ENABLE_SHELL_TRANSITIONS) {
- startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
@@ -690,63 +668,6 @@
extraTransitType, !mIsDropEntering);
}
- /** Launches an activity into split by legacy transition. */
- void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
- final boolean isEnteringSplit = !isSplitActive();
-
- LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback,
- SurfaceControl.Transaction t) {
- if (isEnteringSplit && mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() -> exitSplitScreen(
- null /* childrenToTop */, EXIT_REASON_UNKNOWN));
- Log.w(TAG, splitFailureMessage("startIntentLegacy",
- "side stage was not populated"));
- handleUnsupportedSplitStart();
- }
-
- if (apps != null) {
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
- }
- t.apply();
-
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing legacy transition: ", e);
- }
- }
-
-
- if (!isEnteringSplit && apps != null) {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- prepareEvictNonOpeningChildTasks(position, apps, evictWct);
- mSyncQueue.queue(evictWct);
- }
- }
- };
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
-
- // If split still not active, apply windows bounds first to avoid surface reset to
- // wrong pos by SurfaceAnimator from wms.
- if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
- updateWindowBounds(mSplitLayout, wct);
- }
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
- }
-
/** Starts 2 tasks in one transition. */
void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@@ -991,373 +912,12 @@
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
}
- /** Starts a pair of tasks using legacy transition. */
- void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
- int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (options1 == null) options1 = new Bundle();
- if (taskId2 == INVALID_TASK_ID) {
- // Launching a solo task.
- // Exit split first if this task under split roots.
- if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
- }
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.startTask(taskId1, options1);
- mSyncQueue.queue(wct);
- return;
- }
-
- addActivityOptions(options1, mSideStage);
- wct.startTask(taskId1, options1);
- mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition);
- startWithLegacyTransition(wct, taskId2, options2, splitPosition, snapPosition, adapter,
- instanceId);
- }
-
- /** Starts a pair of intents using legacy transition. */
- void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
- @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
- @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (options1 == null) options1 = new Bundle();
- if (pendingIntent2 == null) {
- // Launching a solo intent or shortcut as fullscreen.
- launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
- options1, adapter, wct);
- return;
- }
-
- addActivityOptions(options1, mSideStage);
- if (shortcutInfo1 != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
- } else {
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- mSplitRequest = new SplitRequest(pendingIntent1.getIntent(),
- pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition);
- }
- startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
- splitPosition, snapPosition, adapter, instanceId);
- }
-
- void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (options1 == null) options1 = new Bundle();
- if (taskId == INVALID_TASK_ID) {
- // Launching a solo intent as fullscreen.
- launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
- adapter, wct);
- return;
- }
-
- addActivityOptions(options1, mSideStage);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition);
- startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter,
- instanceId);
- }
-
- /** Starts a pair of shortcut and task using legacy transition. */
- void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (options1 == null) options1 = new Bundle();
- if (taskId == INVALID_TASK_ID) {
- // Launching a solo shortcut as fullscreen.
- launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
- return;
- }
-
- addActivityOptions(options1, mSideStage);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- startWithLegacyTransition(wct, taskId, options2, splitPosition, snapPosition, adapter,
- instanceId);
- }
-
- private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
- @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
- @Nullable Bundle options, RemoteAnimationAdapter adapter,
- WindowContainerTransaction wct) {
- LegacyTransitions.ILegacyTransition transition =
- (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
- if (apps == null || apps.length == 0) {
- onRemoteAnimationFinished(apps);
- t.apply();
- try {
- adapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- return;
- }
-
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
- t.apply();
-
- try {
- adapter.getRunner().onAnimationStart(
- transit, apps, wallpapers, nonApps, finishedCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- };
-
- addActivityOptions(options, null /* launchTarget */);
- if (shortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
- } else if (pendingIntent != null) {
- wct.sendPendingIntent(pendingIntent, fillInIntent, options);
- } else {
- Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
- }
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
- }
-
- private void startWithLegacyTransition(WindowContainerTransaction wct,
- @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
- @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
- mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId);
- }
-
- private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition,
- @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
- startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
- null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
- snapPosition, adapter, instanceId);
- }
-
- /**
- * @param wct transaction to start the first task
- * @param instanceId if {@code null}, will not log. Otherwise it will be used in
- * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
- */
- private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
- @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
- @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
- if (!isSplitScreenVisible()) {
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
- }
-
- // Init divider first to make divider leash for remote animation target.
- mSplitLayout.init();
- mSplitLayout.setDivideRatio(snapPosition);
-
- // Apply surface bounds before animation start.
- SurfaceControl.Transaction startT = mTransactionPool.acquire();
- updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
- startT.apply();
- mTransactionPool.release(startT);
-
- // Set false to avoid record new bounds with old task still on top;
- mShouldUpdateRecents = false;
- mIsDividerRemoteAnimating = true;
- if (mSplitRequest == null) {
- mSplitRequest = new SplitRequest(mainTaskId,
- mainPendingIntent != null ? mainPendingIntent.getIntent() : null,
- sidePosition);
- }
- setSideStagePosition(sidePosition, wct);
- if (!mMainStage.isActive()) {
- mMainStage.activate(wct, false /* reparent */);
- }
-
- if (options == null) options = new Bundle();
- addActivityOptions(options, mMainStage);
-
- updateWindowBounds(mSplitLayout, wct);
- wct.reorder(mRootTaskInfo.token, true);
- setRootForceTranslucent(false, wct);
-
- // TODO(b/268008375): Merge APIs to start a split pair into one.
- if (mainTaskId != INVALID_TASK_ID) {
- options = wrapAsSplitRemoteAnimation(adapter, options);
- wct.startTask(mainTaskId, options);
- mSyncQueue.queue(wct);
- } else {
- if (mainShortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
- } else {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
- }
- mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
- }
-
- setEnterInstanceId(instanceId);
- }
-
- private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) {
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- if (isSplitScreenVisible()) {
- mMainStage.evictAllChildren(evictWct);
- mSideStage.evictAllChildren(evictWct);
- }
-
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- IRemoteAnimationFinishedCallback wrapCallback =
- new IRemoteAnimationFinishedCallback.Stub() {
- @Override
- public void onAnimationFinished() throws RemoteException {
- onRemoteAnimationFinishedOrCancelled(evictWct);
- finishedCallback.onAnimationFinished();
- }
- };
- Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
- try {
- adapter.getRunner().onAnimationStart(transit, apps, wallpapers,
- ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
- getDividerBarLegacyTarget()), wrapCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- onRemoteAnimationFinishedOrCancelled(evictWct);
- setDividerVisibility(true, null);
- try {
- adapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
- };
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
- wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- return activityOptions.toBundle();
- }
-
- private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
- RemoteAnimationAdapter adapter) {
- LegacyTransitions.ILegacyTransition transition =
- (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
- if (apps == null || apps.length == 0) {
- onRemoteAnimationFinished(apps);
- t.apply();
- try {
- adapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- return;
- }
-
- // Wrap the divider bar into non-apps target to animate together.
- nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
- getDividerBarLegacyTarget());
-
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- // Reset the surface position of the opening app to prevent offset.
- t.setPosition(apps[i].leash, 0, 0);
- }
- }
- setDividerVisibility(true, t);
- t.apply();
-
- IRemoteAnimationFinishedCallback wrapCallback =
- new IRemoteAnimationFinishedCallback.Stub() {
- @Override
- public void onAnimationFinished() throws RemoteException {
- onRemoteAnimationFinished(apps);
- finishedCallback.onAnimationFinished();
- }
- };
- Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
- try {
- adapter.getRunner().onAnimationStart(
- transit, apps, wallpapers, nonApps, wrapCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- };
-
- return transition;
- }
-
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
}
}
- private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
- mIsDividerRemoteAnimating = false;
- mShouldUpdateRecents = true;
- clearRequestIfPresented();
- // If any stage has no child after animation finished, it means that split will display
- // nothing, such status will happen if task and intent is same app but not support
- // multi-instance, we should exit split and expand that app as full screen.
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() ->
- exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
- "main or side stage was not populated."));
- handleUnsupportedSplitStart();
- } else {
- mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- });
- }
- }
-
- private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
- mIsDividerRemoteAnimating = false;
- mShouldUpdateRecents = true;
- clearRequestIfPresented();
- // If any stage has no child after finished animation, that side of the split will display
- // nothing. This might happen if starting the same app on the both sides while not
- // supporting multi-instance. Exit the split screen and expand that app to full screen.
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
- "main or side stage was not populated"));
- handleUnsupportedSplitStart();
- return;
- }
-
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mMainStage.evictNonOpeningChildren(apps, evictWct);
- mSideStage.evictNonOpeningChildren(apps, evictWct);
- mSyncQueue.queue(evictWct);
- }
-
void prepareEvictNonOpeningChildTasks(@SplitPosition int position, RemoteAnimationTarget[] apps,
WindowContainerTransaction wct) {
if (position == mSideStagePosition) {
@@ -1450,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);
});
});
@@ -1576,18 +1137,13 @@
return;
}
- if (ENABLE_SHELL_TRANSITIONS) {
- // Need manually clear here due to this transition might be aborted due to keyguard
- // on top and lead to no visible change.
- clearSplitPairedInRecents(reason);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mLastActiveStage, wct);
- mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
- setSplitsVisible(false);
- } else {
- exitSplitScreen(mLastActiveStage == STAGE_TYPE_MAIN ? mMainStage : mSideStage, reason);
- }
-
+ // Need manually clear here due to this transition might be aborted due to keyguard
+ // on top and lead to no visible change.
+ clearSplitPairedInRecents(reason);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mLastActiveStage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason);
+ setSplitsVisible(false);
mBreakOnNextWake = false;
}
@@ -1596,26 +1152,6 @@
}
/** Exits split screen with legacy transition */
- void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
- toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
- if (!mMainStage.isActive()) return;
-
- StageTaskListener childrenToTop = null;
- if (mMainStage.containsTask(toTopTaskId)) {
- childrenToTop = mMainStage;
- } else if (mSideStage.containsTask(toTopTaskId)) {
- childrenToTop = mSideStage;
- }
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (childrenToTop != null) {
- childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
- }
- applyExitSplitScreen(childrenToTop, wct, exitReason);
- }
-
- /** Exits split screen with legacy transition */
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1865,15 +1401,9 @@
boolean resizeAnim) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b",
taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
- if (!ENABLE_SHELL_TRANSITIONS) {
- // Legacy transition we need to create divider here, shell transition case we will
- // create it on #finishEnterSplitScreen
- mSplitLayout.init();
- } else {
- // We handle split visibility itself on shell transition, but sometimes we didn't
- // reset it correctly after dismiss by some reason, so just set invisible before active.
- setSplitsVisible(false);
- }
+ // We handle split visibility itself on shell transition, but sometimes we didn't
+ // reset it correctly after dismiss by some reason, so just set invisible before active.
+ setSplitsVisible(false);
if (taskInfo != null) {
setSideStagePosition(startPosition, wct);
mSideStage.addTask(taskInfo, wct);
@@ -2420,10 +1950,6 @@
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
- if (!ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(toTopStage, exitReason);
- return;
- }
final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -2481,28 +2007,13 @@
}
sendOnBoundsChanged();
- if (ENABLE_SHELL_TRANSITIONS) {
- mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
- mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
- mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
- }, (finishWct, t) -> {
- mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
- }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
- } else {
- // Only need screenshot for legacy case because shell transition should screenshot
- // itself during transition.
- final SurfaceControl.Transaction startT = mTransactionPool.acquire();
- mMainStage.screenshotIfNeeded(startT);
- mSideStage.screenshotIfNeeded(startT);
- mTransactionPool.release(startT);
+ mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
+ mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
+ }, (finishWct, t) -> {
+ mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
+ }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
- mMainStage.onResized(t);
- mSideStage.onResized(t);
- });
- }
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
@@ -2732,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.
@@ -2747,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
@@ -2820,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.
@@ -2837,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;
}
/**
@@ -3603,16 +3112,6 @@
info.addChange(barChange);
}
- RemoteAnimationTarget getDividerBarLegacyTarget() {
- final Rect bounds = mSplitLayout.getDividerBounds();
- return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
- mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
- null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
- new android.graphics.Point(0, 0) /* position */, bounds, bounds,
- new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
- null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
- }
-
@NeverCompile
@Override
public void dump(@NonNull PrintWriter pw, String prefix) {
@@ -3663,30 +3162,10 @@
mIsDropEntering = true;
mSkipEvictingMainStageChildren = true;
}
- if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
- // If split running background, exit split first.
- // Skip this on shell transition due to we could evict existing tasks on transition
- // finished.
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
- }
mLogger.enterRequestedByDrag(position, dragSessionId);
}
/**
- * Sets info to be logged when splitscreen is next entered.
- */
- public void onRequestToSplit(InstanceId sessionId, @SplitEnterReason int enterReason) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRequestToSplit: reason=%d", enterReason);
- if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
- // If split running background, exit split first.
- // Skip this on shell transition due to we could evict existing tasks on transition
- // finished.
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
- }
- mLogger.enterRequested(sessionId, enterReason);
- }
-
- /**
* Logs the exit of splitscreen.
*/
private void logExit(@ExitReason int exitReason) {
@@ -3768,12 +3247,6 @@
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
if (mMainStage.isActive()) {
final boolean isMainStage = mMainStageListener == this;
- if (!ENABLE_SHELL_TRANSITIONS) {
- StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
- EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- handleUnsupportedSplitStart();
- return;
- }
// If visible, we preserve the app and keep it running. If an app becomes
// unsupported in the bg, break split without putting anything on top
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/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 90eb22f..759f97f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.startingsurface;
import static android.content.Context.CONTEXT_RESTRICTED;
+import static android.content.res.Configuration.UI_MODE_NIGHT_MASK;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -191,31 +192,15 @@
}
final Configuration taskConfig = taskInfo.getConfiguration();
- if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
+ final Configuration contextConfig = context.getResources().getConfiguration();
+ if ((taskConfig.uiMode & UI_MODE_NIGHT_MASK)
+ != (contextConfig.uiMode & UI_MODE_NIGHT_MASK)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"addSplashScreen: creating context based on task Configuration %s",
taskConfig);
final Context overrideContext = context.createConfigurationContext(taskConfig);
overrideContext.setTheme(theme);
- final TypedArray typedArray = overrideContext.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- try {
- if (resId != 0 && overrideContext.getDrawable(resId) != null) {
- // We want to use the windowBackground for the override context if it is
- // available, otherwise we use the default one to make sure a themed starting
- // window is displayed for the app.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen: apply overrideConfig %s",
- taskConfig);
- context = overrideContext;
- }
- } catch (Resources.NotFoundException e) {
- Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
- + taskId, e);
- return null;
- }
- typedArray.recycle();
+ context = overrideContext;
}
return context;
}
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/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 58cf398..c850ff8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -40,6 +40,7 @@
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -821,7 +822,8 @@
}
// The change has already animated by back gesture, don't need to play transition
// animation on it.
- if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
+ if (!migratePredictiveBackTransition()
+ && change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
info.getChanges().remove(i);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 9de0651..401b78d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -30,6 +30,7 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
@@ -37,10 +38,12 @@
import android.os.Handler;
import android.util.Size;
import android.view.Choreographer;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
@@ -195,7 +198,8 @@
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw,
- boolean setTaskCropAndPosition) {
+ boolean setTaskCropAndPosition,
+ InsetsState displayInsetsState) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
@@ -223,6 +227,8 @@
controlsElement.mWidthResId = R.dimen.caption_right_buttons_width;
controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
relayoutParams.mOccludingCaptionElements.add(controlsElement);
+ relayoutParams.mCaptionTopPadding = getTopPadding(relayoutParams,
+ taskInfo.getConfiguration().windowConfiguration.getBounds(), displayInsetsState);
}
@SuppressLint("MissingPermission")
@@ -238,7 +244,7 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
- setTaskCropAndPosition);
+ setTaskCropAndPosition, mDisplayController.getInsetsState(taskInfo.displayId));
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -344,6 +350,18 @@
mDragResizeListener = null;
}
+ private static int getTopPadding(RelayoutParams params, Rect taskBounds,
+ InsetsState insetsState) {
+ if (!params.mRunningTaskInfo.isFreeform()) {
+ Insets systemDecor = insetsState.calculateInsets(taskBounds,
+ WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
+ false /* ignoreVisibility */);
+ return systemDecor.top;
+ } else {
+ return 0;
+ }
+ }
+
/**
* Checks whether the touch event falls inside the customizable caption region.
*/
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 633067d..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
@@ -30,7 +30,6 @@
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_OUTSIDE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -70,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;
@@ -90,6 +88,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
+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;
@@ -115,9 +114,12 @@
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;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Supplier;
@@ -174,6 +176,7 @@
private boolean mInImmersiveMode;
private final String mSysUIPackageName;
+ private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
private final ISystemGestureExclusionListener mGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
@Override
@@ -286,6 +289,31 @@
mSysUIPackageName = mContext.getResources().getString(
com.android.internal.R.string.config_systemUi);
mInteractionJankMonitor = interactionJankMonitor;
+ mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
+ DesktopModeWindowDecoration decoration;
+ RunningTaskInfo taskInfo;
+ for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+ decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration == null) {
+ continue;
+ } else {
+ taskInfo = decoration.mTaskInfo;
+ }
+
+ // Check if display has been rotated between portrait & landscape
+ if (displayId == taskInfo.displayId && taskInfo.isFreeform()
+ && (fromRotation % 2 != toRotation % 2)) {
+ // Check if the task bounds on the rotated display will be out of bounds.
+ // If so, then update task bounds to be within reachable area.
+ final Rect taskBounds = new Rect(
+ taskInfo.configuration.windowConfiguration.getBounds());
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, decoration.calculateValidDragArea())) {
+ t.setBounds(taskInfo.token, taskBounds);
+ }
+ }
+ }
+ };
shellInit.addInitCallback(this::onInit, this);
}
@@ -293,10 +321,13 @@
private void onInit() {
mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
mShellCommandHandler.addDumpCallback(this::dump, this);
- mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
+ mDisplayInsetsController.addGlobalInsetsChangedListener(
new DesktopModeOnInsetsChangedListener());
mDesktopTasksController.setOnTaskResizeAnimationListener(
- new DeskopModeOnTaskResizeAnimationListener());
+ new DesktopModeOnTaskResizeAnimationListener());
+ mDesktopTasksController.setOnTaskRepositionAnimationListener(
+ new DesktopModeOnTaskRepositionAnimationListener());
+ mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
mContext.getDisplayId());
@@ -418,13 +449,13 @@
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
- private void onMaximizeOrRestore(int taskId, String tag) {
+ private void onMaximizeOrRestore(int taskId, String source) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
}
mInteractionJankMonitor.begin(
- decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, tag);
+ decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source);
mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
@@ -435,16 +466,28 @@
if (decoration == null) {
return;
}
- mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
- left ? SnapPosition.LEFT : SnapPosition.RIGHT);
+
+ if (!decoration.mTaskInfo.isResizeable
+ && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(mContext)) {
+ //TODO(b/354658237) - show toast with relevant string
+ } else {
+ mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
+ decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
+ left ? SnapPosition.LEFT : SnapPosition.RIGHT);
+ }
+
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
- private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) {
+ private void onOpenInBrowser(int taskId, @NonNull Uri uri) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
openInBrowser(uri);
- decor.closeHandleMenu();
- decor.closeMaximizeMenu();
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
}
private void openInBrowser(Uri uri) {
@@ -454,6 +497,57 @@
mContext.startActivity(intent);
}
+ private void onToDesktop(int taskId, DesktopModeTransitionSource source) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+ CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ decoration.addCaptionInset(wct);
+ mDesktopTasksController.moveTaskToDesktop(taskId, wct, source);
+ decoration.closeHandleMenu();
+ }
+
+ private void onToFullscreen(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ if (isTaskInSplitScreen(taskId)) {
+ mSplitScreenController.moveTaskToFullscreen(taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE);
+ } else {
+ mDesktopTasksController.moveToFullscreen(taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
+ }
+ }
+
+ private void onToSplitScreen(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ // When the app enters split-select, the handle will no longer be visible, meaning
+ // we shouldn't receive input for it any longer.
+ decoration.disposeStatusBarInputLayer();
+ mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
+ }
+
+ private void onNewWindow(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ decoration.closeHandleMenu();
+ mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
@@ -464,6 +558,7 @@
private final DragDetector mDragDetector;
private final GestureDetector mGestureDetector;
private final int mDisplayId;
+ private final Rect mOnDragStartInitialBounds = new Rect();
/**
* Whether to pilfer the next motion event to send cancellations to the windows below.
@@ -511,40 +606,6 @@
moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu(mSplitScreenController);
}
- } else if (id == R.id.desktop_button) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // App sometimes draws before the insets from WindowDecoration#relayout have
- // been added, so they must be added here
- mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
- CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
- mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.moveTaskToDesktop(mTaskId, wct,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
- decoration.closeHandleMenu();
- } else if (id == R.id.fullscreen_button) {
- decoration.closeHandleMenu();
- if (isTaskInSplitScreen(mTaskId)) {
- mSplitScreenController.moveTaskToFullscreen(mTaskId,
- SplitScreenController.EXIT_REASON_DESKTOP_MODE);
- } else {
- mDesktopTasksController.moveToFullscreen(mTaskId,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
- }
- } else if (id == R.id.split_screen_button) {
- decoration.closeHandleMenu();
- // When the app enters split-select, the handle will no longer be visible, meaning
- // we shouldn't receive input for it any longer.
- decoration.disposeStatusBarInputLayer();
- mDesktopTasksController.requestSplit(decoration.mTaskInfo);
- } else if (id == R.id.open_in_browser_button) {
- // TODO(b/346441962): let the decoration handle the click gesture and only call back
- // to the ViewModel via #setOpenInBrowserClickListener
- decoration.onOpenInBrowserClick();
- } else if (id == R.id.collapse_menu_button) {
- decoration.closeHandleMenu();
- } else if (id == R.id.new_window_button) {
- decoration.closeHandleMenu();
- mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
} else if (id == R.id.maximize_window) {
// TODO(b/346441962): move click detection logic into the decor's
// {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
@@ -559,16 +620,6 @@
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- if (e.getActionMasked() == ACTION_OUTSIDE) {
- if (id == R.id.handle_menu) {
- // Close handle menu on outside touch if menu is directly touchable; if not,
- // it will be handled by handleEventOutsideCaption.
- if (decoration.mTaskInfo.isFreeform()
- || Flags.enableAdditionalWindowsAboveStatusBar()) {
- decoration.closeHandleMenu();
- }
- }
- }
if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
mTouchscreenInUse = e.getActionMasked() != ACTION_UP
&& e.getActionMasked() != ACTION_CANCEL;
@@ -578,13 +629,21 @@
&& id != R.id.maximize_window) {
return false;
}
- moveTaskToFront(decoration.mTaskInfo);
final int actionMasked = e.getActionMasked();
final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
|| actionMasked == MotionEvent.ACTION_UP;
if (isDown) {
+ // Only move to front on down to prevent 2+ tasks from fighting
+ // (and thus flickering) for front status when drag-moving them simultaneously with
+ // two pointers.
+ // TODO(b/356962065): during a drag-move, this shouldn't be a WCT - just move the
+ // task surface to the top of other tasks and reorder once the user releases the
+ // gesture together with the bounds' WCT. This is probably still valid for other
+ // gestures like simple clicks.
+ moveTaskToFront(decoration.mTaskInfo);
+
final boolean downInCustomizableCaptionRegion =
decoration.checkTouchEventInCustomizableRegion(e);
final boolean downInExclusionRegion = mExclusionRegion.contains(
@@ -711,10 +770,11 @@
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
- mDragPositioningCallback.onDragPositioningStart(
+ final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
updateDragStatus(e.getActionMasked());
+ mOnDragStartInitialBounds.set(initialBounds);
mHasLongClicked = false;
// Do not consume input event if a button is touched, otherwise it would
// prevent the button's ripple effect from showing.
@@ -755,9 +815,14 @@
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
+ // Tasks bounds haven't actually been updated (only its leash), so pass to
+ // DesktopTasksController to allow secondary transformations (i.e. snap resizing
+ // or transforming to fullscreen) before setting new task bounds.
+ mDesktopTasksController.onDragPositioningEnd(
+ taskInfo, decoration.mTaskSurface, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds, decoration.calculateValidDragArea());
+ newTaskBounds, decoration.calculateValidDragArea(),
+ new Rect(mOnDragStartInitialBounds));
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -1131,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;
@@ -1176,13 +1237,13 @@
final DragPositioningCallback dragPositioningCallback;
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
- dragPositioningCallback = new FluidResizeTaskPositioner(
+ dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
mDragStartListener, mTransactionFactory);
windowDecoration.setTaskDragResizer(
(FluidResizeTaskPositioner) dragPositioningCallback);
} else {
- dragPositioningCallback = new VeiledResizeTaskPositioner(
+ dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
mDragStartListener, mTransitions, mInteractionJankMonitor);
windowDecoration.setTaskDragResizer(
@@ -1191,14 +1252,36 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
- windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore);
- windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> {
- onSnapResize(taskId, true /* isLeft */);
+ windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
+ onMaximizeOrRestore(taskInfo.taskId, "maximize_menu");
+ return Unit.INSTANCE;
});
- windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
- onSnapResize(taskId, false /* isLeft */);
+ windowDecoration.setOnLeftSnapClickListener(() -> {
+ onSnapResize(taskInfo.taskId, true /* isLeft */);
+ return Unit.INSTANCE;
});
- windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser);
+ windowDecoration.setOnRightSnapClickListener(() -> {
+ onSnapResize(taskInfo.taskId, false /* isLeft */);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
+ onToDesktop(taskInfo.taskId, desktopModeTransitionSource);
+ });
+ windowDecoration.setOnToFullscreenClickListener(() -> {
+ onToFullscreen(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOnToSplitScreenClickListener(() -> {
+ onToSplitScreen(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
+ windowDecoration.setOpenInBrowserClickListener((uri) -> {
+ onOpenInBrowser(taskInfo.taskId, uri);
+ });
+ windowDecoration.setOnNewWindowClickListener(() -> {
+ onNewWindow(taskInfo.taskId);
+ return Unit.INSTANCE;
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
@@ -1233,7 +1316,26 @@
pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
}
- private class DeskopModeOnTaskResizeAnimationListener
+ private class DesktopModeOnTaskRepositionAnimationListener
+ implements OnTaskRepositionAnimationListener {
+ @Override
+ public void onAnimationStart(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration != null) {
+ decoration.setAnimatingTaskResizeOrReposition(true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(int taskId) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration != null) {
+ decoration.setAnimatingTaskResizeOrReposition(false);
+ }
+ }
+ }
+
+ private class DesktopModeOnTaskResizeAnimationListener
implements OnTaskResizeAnimationListener {
@Override
public void onAnimationStart(int taskId, Transaction t, Rect bounds) {
@@ -1243,7 +1345,7 @@
return;
}
decoration.showResizeVeil(t, bounds);
- decoration.setAnimatingTaskResize(true);
+ decoration.setAnimatingTaskResizeOrReposition(true);
}
@Override
@@ -1258,7 +1360,7 @@
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) return;
decoration.hideResizeVeil();
- decoration.setAnimatingTaskResize(false);
+ decoration.setAnimatingTaskResizeOrReposition(false);
}
}
@@ -1291,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);
+ }
+ }
}
}
@@ -1311,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/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b888344..538d0fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -26,6 +26,7 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -77,18 +78,20 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder;
import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -115,9 +118,13 @@
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
- private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener;
- private OnTaskActionClickListener mOnLeftSnapClickListener;
- private OnTaskActionClickListener mOnRightSnapClickListener;
+ private Function0<Unit> mOnMaximizeOrRestoreClickListener;
+ private Function0<Unit> mOnLeftSnapClickListener;
+ private Function0<Unit> mOnRightSnapClickListener;
+ private Consumer<DesktopModeTransitionSource> mOnToDesktopClickListener;
+ private Function0<Unit> mOnToFullscreenClickListener;
+ private Function0<Unit> mOnToSplitscreenClickListener;
+ private Function0<Unit> mOnNewWindowClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -140,7 +147,7 @@
private CharSequence mAppName;
private CapturedLink mCapturedLink;
private Uri mGenericLink;
- private OpenInBrowserClickListener mOpenInBrowserClickListener;
+ private Consumer<Uri> mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
@@ -228,24 +235,42 @@
* TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
* having the ViewModel deal with parsing motion events.
*/
- void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) {
+ void setOnMaximizeOrRestoreClickListener(Function0<Unit> listener) {
mOnMaximizeOrRestoreClickListener = listener;
}
- /**
- * Register a listener to be called back when one of the tasks snap-left action is triggered.
- */
- void setOnLeftSnapClickListener(OnTaskActionClickListener listener) {
+ /** Registers a listener to be called when the decoration's snap-left action is triggered.*/
+ void setOnLeftSnapClickListener(Function0<Unit> listener) {
mOnLeftSnapClickListener = listener;
}
- /**
- * Register a listener to be called back when one of the tasks' snap-right action is triggered.
- */
- void setOnRightSnapClickListener(OnTaskActionClickListener listener) {
+ /** Registers a listener to be called when the decoration's snap-right action is triggered. */
+ void setOnRightSnapClickListener(Function0<Unit> listener) {
mOnRightSnapClickListener = listener;
}
+ /** Registers a listener to be called when the decoration's to-desktop action is triggered. */
+ void setOnToDesktopClickListener(Consumer<DesktopModeTransitionSource> listener) {
+ mOnToDesktopClickListener = listener;
+ }
+
+ /**
+ * Registers a listener to be called when the decoration's to-fullscreen action is triggered.
+ */
+ void setOnToFullscreenClickListener(Function0<Unit> listener) {
+ mOnToFullscreenClickListener = listener;
+ }
+
+ /** Registers a listener to be called when the decoration's to-split action is triggered. */
+ void setOnToSplitScreenClickListener(Function0<Unit> listener) {
+ mOnToSplitscreenClickListener = listener;
+ }
+
+ /** Registers a listener to be called when the decoration's new window action is triggered. */
+ void setOnNewWindowClickListener(Function0<Unit> listener) {
+ mOnNewWindowClickListener = listener;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
@@ -270,7 +295,7 @@
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
- void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) {
+ void setOpenInBrowserClickListener(Consumer<Uri> listener) {
mOpenInBrowserClickListener = listener;
}
@@ -287,8 +312,7 @@
// transaction (that applies task crop) is synced with the buffer transaction (that draws
// the View). Both will be shown on screen at the same, whereas applying them independently
// causes flickering. See b/270202228.
- final boolean applyTransactionOnDraw =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop);
if (!applyTransactionOnDraw) {
t.apply();
@@ -299,7 +323,7 @@
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (taskInfo.isFreeform()) {
// The Task is in Freeform mode -> show its header in sync since it's an integral part
// of the window itself - a delayed header might cause bad UX.
relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
@@ -445,14 +469,6 @@
}
}
- void onOpenInBrowserClick() {
- if (mOpenInBrowserClickListener == null || mHandleMenu == null) {
- return;
- }
- mOpenInBrowserClickListener.onClick(this, mHandleMenu.getOpenInBrowserLink());
- onCapturedLinkExpired();
- }
-
@Nullable
private Uri getBrowserLink() {
// If the captured link is available and has not expired, return the captured link.
@@ -507,9 +523,7 @@
}
private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
- final boolean isFreeform =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- return isFreeform && taskInfo.isResizeable;
+ return taskInfo.isFreeform() && taskInfo.isResizeable;
}
private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
@@ -965,11 +979,8 @@
mHandleMenu = mHandleMenuFactory.create(
this,
mRelayoutParams.mLayoutResId,
- mOnCaptionButtonClickListener,
- mOnCaptionTouchListener,
mAppIconBitmap,
mAppName,
- mDisplayController,
splitScreenController,
DesktopModeStatus.canEnterDesktopMode(mContext),
Flags.enableDesktopWindowingMultiInstanceFeatures()
@@ -981,7 +992,28 @@
mResult.mCaptionX
);
mWindowDecorViewHolder.onHandleMenuOpened();
- mHandleMenu.show();
+ mHandleMenu.show(
+ /* onToDesktopClickListener= */ () -> {
+ mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
+ return Unit.INSTANCE;
+ },
+ /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
+ /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
+ /* onNewWindowClickListener= */ mOnNewWindowClickListener,
+ /* openInBrowserClickListener= */ (uri) -> {
+ mOpenInBrowserClickListener.accept(uri);
+ onCapturedLinkExpired();
+ return Unit.INSTANCE;
+ },
+ /* onCloseMenuClickListener= */ () -> {
+ closeHandleMenu();
+ return Unit.INSTANCE;
+ },
+ /* onOutsideTouchListener= */ () -> {
+ closeHandleMenu();
+ return Unit.INSTANCE;
+ }
+ );
}
private void updateGenericLink() {
@@ -1236,10 +1268,10 @@
return R.id.desktop_mode_caption;
}
- void setAnimatingTaskResize(boolean animatingTaskResize) {
+ void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
((AppHeaderViewHolder) mWindowDecorViewHolder)
- .setAnimatingTaskResize(animatingTaskResize);
+ .setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition);
}
/**
@@ -1320,14 +1352,6 @@
}
}
-
- /** Listener for the handle menu's "Open in browser" button */
- interface OpenInBrowserClickListener {
-
- /** Inform the implementing class that the "Open in browser" button has been clicked */
- void onClick(DesktopModeWindowDecoration decoration, Uri uri);
- }
-
interface ExclusionRegionListener {
/** Inform the implementing class of this task's change in region resize handles */
void onExclusionRegionChanged(int taskId, Region region);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 1729548..a27c506 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.windowdecor;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -112,7 +113,7 @@
mDecorationSurface,
mClientToken,
null /* hostInputToken */,
- FLAG_NOT_FOCUSABLE,
+ FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
PRIVATE_FLAG_TRUSTED_OVERLAY,
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 76096b0..e2d42b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -152,11 +152,8 @@
}
mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
- wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mTaskBoundsAtDragStart.setEmpty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 74a6407..c16c16f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -17,7 +17,8 @@
import android.annotation.ColorInt
import android.annotation.DimenRes
-import android.app.ActivityManager
+import android.annotation.SuppressLint
+import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources
@@ -28,7 +29,9 @@
import android.graphics.PointF
import android.graphics.Rect
import android.net.Uri
+import android.view.LayoutInflater
import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_OUTSIDE
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
@@ -42,7 +45,6 @@
import androidx.core.view.isGone
import com.android.window.flags.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -63,22 +65,18 @@
class HandleMenu(
private val parentDecor: DesktopModeWindowDecoration,
private val layoutResId: Int,
- private val onClickListener: View.OnClickListener?,
- private val onTouchListener: View.OnTouchListener?,
private val appIconBitmap: Bitmap?,
private val appName: CharSequence?,
- private val displayController: DisplayController,
private val splitScreenController: SplitScreenController,
private val shouldShowWindowingPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
- val openInBrowserLink: Uri?,
+ private val openInBrowserLink: Uri?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int
) {
private val context: Context = parentDecor.mDecorWindowContext
- private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo
- private val decorThemeUtil = DecorThemeUtil(context)
+ private val taskInfo: RunningTaskInfo = parentDecor.mTaskInfo
private val isViewAboveStatusBar: Boolean
get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
@@ -94,10 +92,9 @@
private val marginMenuStart = loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_margin_start)
- private var handleMenuAnimator: HandleMenuAnimator? = null
-
@VisibleForTesting
var handleMenuViewContainer: AdditionalViewContainer? = null
+ private var handleMenuView: HandleMenuView? = null
// Position of the handle menu used for laying out the handle view.
@VisibleForTesting
@@ -116,168 +113,89 @@
updateHandleMenuPillPositions(captionX)
}
- fun show() {
+ fun show(
+ onToDesktopClickListener: () -> Unit,
+ onToFullscreenClickListener: () -> Unit,
+ onToSplitScreenClickListener: () -> Unit,
+ onNewWindowClickListener: () -> Unit,
+ openInBrowserClickListener: (Uri) -> Unit,
+ onCloseMenuClickListener: () -> Unit,
+ onOutsideTouchListener: () -> Unit,
+ ) {
val ssg = SurfaceSyncGroup(TAG)
val t = SurfaceControl.Transaction()
- createHandleMenuViewContainer(t, ssg)
+ createHandleMenu(
+ t = t,
+ ssg = ssg,
+ onToDesktopClickListener = onToDesktopClickListener,
+ onToFullscreenClickListener = onToFullscreenClickListener,
+ onToSplitScreenClickListener = onToSplitScreenClickListener,
+ onNewWindowClickListener = onNewWindowClickListener,
+ openInBrowserClickListener = openInBrowserClickListener,
+ onCloseMenuClickListener = onCloseMenuClickListener,
+ onOutsideTouchListener = onOutsideTouchListener,
+ )
ssg.addTransaction(t)
ssg.markSyncReady()
- setupHandleMenu()
- animateHandleMenu()
+
+ handleMenuView?.animateOpenMenu()
}
- private fun createHandleMenuViewContainer(
+ private fun createHandleMenu(
t: SurfaceControl.Transaction,
- ssg: SurfaceSyncGroup
+ ssg: SurfaceSyncGroup,
+ onToDesktopClickListener: () -> Unit,
+ onToFullscreenClickListener: () -> Unit,
+ onToSplitScreenClickListener: () -> Unit,
+ onNewWindowClickListener: () -> Unit,
+ openInBrowserClickListener: (Uri) -> Unit,
+ onCloseMenuClickListener: () -> Unit,
+ onOutsideTouchListener: () -> Unit
) {
+ val handleMenuView = HandleMenuView(
+ context = context,
+ menuWidth = menuWidth,
+ captionHeight = captionHeight,
+ shouldShowWindowingPill = shouldShowWindowingPill,
+ shouldShowBrowserPill = shouldShowBrowserPill,
+ shouldShowNewWindowButton = shouldShowNewWindowButton
+ ).apply {
+ bind(taskInfo, appIconBitmap, appName)
+ this.onToDesktopClickListener = onToDesktopClickListener
+ this.onToFullscreenClickListener = onToFullscreenClickListener
+ this.onToSplitScreenClickListener = onToSplitScreenClickListener
+ this.onNewWindowClickListener = onNewWindowClickListener
+ this.onOpenInBrowserClickListener = {
+ openInBrowserClickListener.invoke(openInBrowserLink!!)
+ }
+ this.onCloseMenuClickListener = onCloseMenuClickListener
+ this.onOutsideTouchListener = onOutsideTouchListener
+ }
+
val x = handleMenuPosition.x.toInt()
val y = handleMenuPosition.y.toInt()
handleMenuViewContainer =
if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
AdditionalSystemViewContainer(
context = context,
- layoutId = R.layout.desktop_mode_window_decor_handle_menu,
taskId = taskInfo.taskId,
x = x,
y = y,
width = menuWidth,
height = menuHeight,
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ view = handleMenuView.rootView
)
} else {
parentDecor.addWindow(
- R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
- t, ssg, x, y, menuWidth, menuHeight
+ handleMenuView.rootView, "Handle Menu", t, ssg, x, y, menuWidth, menuHeight
)
}
- handleMenuViewContainer?.view?.let { view ->
- handleMenuAnimator =
- HandleMenuAnimator(view, menuWidth, captionHeight.toFloat())
- }
- }
- /**
- * Animates the appearance of the handle menu and its three pills.
- */
- private fun animateHandleMenu() {
- when {
- taskInfo.isFullscreen || taskInfo.isMultiWindow -> {
- handleMenuAnimator?.animateCaptionHandleExpandToOpen()
- }
- else -> {
- handleMenuAnimator?.animateOpen()
- }
- }
- }
-
- /**
- * Set up all three pills of the handle menu: app info pill, windowing pill, & more actions
- * pill.
- */
- private fun setupHandleMenu() {
- val handleMenu = handleMenuViewContainer?.view ?: return
- handleMenu.setOnTouchListener(onTouchListener)
-
- val style = calculateMenuStyle()
- setupAppInfoPill(handleMenu, style)
- if (shouldShowWindowingPill) {
- setupWindowingPill(handleMenu, style)
- }
- setupMoreActionsPill(handleMenu, style)
- setupOpenInBrowserPill(handleMenu, style)
- }
-
- /**
- * Set up interactive elements of handle menu's app info pill.
- */
- private fun setupAppInfoPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.app_info_pill).apply {
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- pill.requireViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
- .let { collapseBtn ->
- collapseBtn.imageTintList = ColorStateList.valueOf(style.textColor)
- collapseBtn.setOnClickListener(onClickListener)
- collapseBtn.taskInfo = taskInfo
- }
- pill.requireViewById<ImageView>(R.id.application_icon).let { appIcon ->
- appIcon.setImageBitmap(appIconBitmap)
- }
- pill.requireViewById<TextView>(R.id.application_name).let { appNameView ->
- appNameView.text = appName
- appNameView.setTextColor(style.textColor)
- }
- }
-
- /**
- * Set up interactive elements and color of handle menu's windowing pill.
- */
- private fun setupWindowingPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.windowing_pill).apply {
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- val fullscreenBtn = pill.requireViewById<ImageButton>(R.id.fullscreen_button)
- val splitscreenBtn = pill.requireViewById<ImageButton>(R.id.split_screen_button)
- val floatingBtn = pill.requireViewById<ImageButton>(R.id.floating_button)
- // TODO: Remove once implemented.
- floatingBtn.visibility = View.GONE
- val desktopBtn = handleMenu.requireViewById<ImageButton>(R.id.desktop_button)
-
- fullscreenBtn.setOnClickListener(onClickListener)
- splitscreenBtn.setOnClickListener(onClickListener)
- floatingBtn.setOnClickListener(onClickListener)
- desktopBtn.setOnClickListener(onClickListener)
-
- fullscreenBtn.isSelected = taskInfo.isFullscreen
- fullscreenBtn.imageTintList = style.windowingButtonColor
- splitscreenBtn.isSelected = taskInfo.isMultiWindow
- splitscreenBtn.imageTintList = style.windowingButtonColor
- floatingBtn.isSelected = taskInfo.isPinned
- floatingBtn.imageTintList = style.windowingButtonColor
- desktopBtn.isSelected = taskInfo.isFreeform
- desktopBtn.imageTintList = style.windowingButtonColor
- }
-
- /**
- * Set up interactive elements & height of handle menu's more actions pill
- */
- private fun setupMoreActionsPill(handleMenu: View, style: MenuStyle) {
- val moreActionsPill = handleMenu.findViewById<View>(R.id.more_actions_pill)
- moreActionsPill.isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
- if (!moreActionsPill.isGone) {
- handleMenu.requireViewById<Button>(R.id.screenshot_button).apply {
- isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- handleMenu.findViewById<Button>(R.id.new_window_button).apply {
- isGone = !shouldShowNewWindowButton
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- if (!isGone) setOnClickListener(onClickListener)
- }
- }
- }
-
- private fun setupOpenInBrowserPill(handleMenu: View, style: MenuStyle) {
- val pill = handleMenu.requireViewById<View>(R.id.open_in_browser_pill).apply {
- isGone = !shouldShowBrowserPill
- background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
- }
-
- pill.requireViewById<Button>(R.id.open_in_browser_button).let { browserButton ->
- browserButton.setOnClickListener(onClickListener)
- browserButton.setTextColor(style.textColor)
- browserButton.compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
+ this.handleMenuView = handleMenuView
}
/**
@@ -371,16 +289,8 @@
fun checkMotionEvent(ev: MotionEvent) {
// If the menu view is above status bar, we can let the views handle input directly.
if (isViewAboveStatusBar) return
- val handleMenu = handleMenuViewContainer?.view ?: return
- val collapse = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button)
val inputPoint = translateInputToLocalSpace(ev)
- val inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y)
- val action = ev.actionMasked
- collapse.isHovered = inputInCollapseButton && action != MotionEvent.ACTION_UP
- collapse.isPressed = inputInCollapseButton && action == MotionEvent.ACTION_DOWN
- if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
- collapse.performClick()
- }
+ handleMenuView?.checkMotionEvent(ev, inputPoint)
}
// Translate the input point from display coordinates to the same space as the handle menu.
@@ -480,45 +390,241 @@
}
fun close() {
- val after = {
+ handleMenuView?.animateCloseMenu {
handleMenuViewContainer?.releaseView()
handleMenuViewContainer = null
}
- if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
- handleMenuAnimator?.animateCollapseIntoHandleClose(after)
- } else {
- handleMenuAnimator?.animateClose(after)
- }
}
- private fun calculateMenuStyle(): MenuStyle {
- val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
- return MenuStyle(
- backgroundColor = colorScheme.surfaceBright.toArgb(),
- textColor = colorScheme.onSurface.toArgb(),
- windowingButtonColor = ColorStateList(
- arrayOf(
- intArrayOf(android.R.attr.state_pressed),
- intArrayOf(android.R.attr.state_focused),
- intArrayOf(android.R.attr.state_selected),
- intArrayOf(),
+ /** The view within the Handle Menu, with options to change the windowing mode and more. */
+ @SuppressLint("ClickableViewAccessibility")
+ class HandleMenuView(
+ context: Context,
+ menuWidth: Int,
+ captionHeight: Int,
+ private val shouldShowWindowingPill: Boolean,
+ private val shouldShowBrowserPill: Boolean,
+ private val shouldShowNewWindowButton: Boolean
+ ) {
+ val rootView = LayoutInflater.from(context)
+ .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
+
+ // App Info Pill.
+ private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill)
+ private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>(
+ R.id.collapse_menu_button)
+ private val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon)
+ private val appNameView = appInfoPill.requireViewById<TextView>(R.id.application_name)
+
+ // Windowing Pill.
+ private val windowingPill = rootView.requireViewById<View>(R.id.windowing_pill)
+ private val fullscreenBtn = windowingPill.requireViewById<ImageButton>(
+ R.id.fullscreen_button)
+ private val splitscreenBtn = windowingPill.requireViewById<ImageButton>(
+ R.id.split_screen_button)
+ private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button)
+ private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button)
+
+ // More Actions Pill.
+ private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
+ private val screenshotBtn = moreActionsPill.requireViewById<Button>(R.id.screenshot_button)
+ private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
+
+ // Open in Browser Pill.
+ private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
+ private val browserBtn = openInBrowserPill.requireViewById<Button>(
+ R.id.open_in_browser_button)
+
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
+
+ private lateinit var taskInfo: RunningTaskInfo
+ private lateinit var style: MenuStyle
+
+ var onToDesktopClickListener: (() -> Unit)? = null
+ var onToFullscreenClickListener: (() -> Unit)? = null
+ var onToSplitScreenClickListener: (() -> Unit)? = null
+ var onNewWindowClickListener: (() -> Unit)? = null
+ var onOpenInBrowserClickListener: (() -> Unit)? = null
+ var onCloseMenuClickListener: (() -> Unit)? = null
+ var onOutsideTouchListener: (() -> Unit)? = null
+
+ init {
+ fullscreenBtn.setOnClickListener { onToFullscreenClickListener?.invoke() }
+ splitscreenBtn.setOnClickListener { onToSplitScreenClickListener?.invoke() }
+ desktopBtn.setOnClickListener { onToDesktopClickListener?.invoke() }
+ browserBtn.setOnClickListener { onOpenInBrowserClickListener?.invoke() }
+ collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
+ newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
+
+ rootView.setOnTouchListener { _, event ->
+ if (event.actionMasked == ACTION_OUTSIDE) {
+ onOutsideTouchListener?.invoke()
+ return@setOnTouchListener false
+ }
+ return@setOnTouchListener true
+ }
+ }
+
+ /** Binds the menu views to the new data. */
+ fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+ this.taskInfo = taskInfo
+ this.style = calculateMenuStyle(taskInfo)
+
+ bindAppInfoPill(style, appIconBitmap, appName)
+ if (shouldShowWindowingPill) {
+ bindWindowingPill(style)
+ }
+ bindMoreActionsPill(style)
+ bindOpenInBrowserPill(style)
+ }
+
+ /** Animates the menu opening. */
+ fun animateOpenMenu() {
+ if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
+ animator.animateCaptionHandleExpandToOpen()
+ } else {
+ animator.animateOpen()
+ }
+ }
+
+ /** Animates the menu closing. */
+ fun animateCloseMenu(onAnimFinish: () -> Unit) {
+ if (taskInfo.isFullscreen || taskInfo.isMultiWindow) {
+ animator.animateCollapseIntoHandleClose(onAnimFinish)
+ } else {
+ animator.animateClose(onAnimFinish)
+ }
+ }
+
+ /**
+ * Checks whether a motion event falls inside this menu, and invokes a click of the
+ * collapse button if needed.
+ * Note: should only be called when regular click detection doesn't work because input is
+ * detected through the status bar layer with a global input monitor.
+ */
+ fun checkMotionEvent(ev: MotionEvent, inputPointLocal: PointF) {
+ val inputInCollapseButton = pointInView(
+ collapseMenuButton,
+ inputPointLocal.x,
+ inputPointLocal.y
+ )
+ val action = ev.actionMasked
+ collapseMenuButton.isHovered = inputInCollapseButton
+ && action != MotionEvent.ACTION_UP
+ collapseMenuButton.isPressed = inputInCollapseButton
+ && action == MotionEvent.ACTION_DOWN
+ if (action == MotionEvent.ACTION_UP && inputInCollapseButton) {
+ collapseMenuButton.performClick()
+ }
+ }
+
+ private fun pointInView(v: View?, x: Float, y: Float): Boolean {
+ return v != null && v.left <= x && v.right >= x && v.top <= y && v.bottom >= y
+ }
+
+ private fun calculateMenuStyle(taskInfo: RunningTaskInfo): MenuStyle {
+ val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
+ return MenuStyle(
+ backgroundColor = colorScheme.surfaceBright.toArgb(),
+ textColor = colorScheme.onSurface.toArgb(),
+ windowingButtonColor = ColorStateList(
+ arrayOf(
+ intArrayOf(android.R.attr.state_pressed),
+ intArrayOf(android.R.attr.state_focused),
+ intArrayOf(android.R.attr.state_selected),
+ intArrayOf(),
+ ),
+ intArrayOf(
+ colorScheme.onSurface.toArgb(),
+ colorScheme.onSurface.toArgb(),
+ colorScheme.primary.toArgb(),
+ colorScheme.onSurface.toArgb(),
+ )
),
- intArrayOf(
- colorScheme.onSurface.toArgb(),
- colorScheme.onSurface.toArgb(),
- colorScheme.primary.toArgb(),
- colorScheme.onSurface.toArgb(),
+ )
+ }
+
+ private fun bindAppInfoPill(
+ style: MenuStyle,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?
+ ) {
+ appInfoPill.background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+
+ collapseMenuButton.apply {
+ imageTintList = ColorStateList.valueOf(style.textColor)
+ this.taskInfo = this@HandleMenuView.taskInfo
+ }
+ appIconView.setImageBitmap(appIconBitmap)
+ appNameView.apply {
+ text = appName
+ setTextColor(style.textColor)
+ }
+ }
+
+ private fun bindWindowingPill(style: MenuStyle) {
+ windowingPill.background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+
+ // TODO: Remove once implemented.
+ floatingBtn.visibility = View.GONE
+
+ fullscreenBtn.isSelected = taskInfo.isFullscreen
+ fullscreenBtn.imageTintList = style.windowingButtonColor
+ splitscreenBtn.isSelected = taskInfo.isMultiWindow
+ splitscreenBtn.imageTintList = style.windowingButtonColor
+ floatingBtn.isSelected = taskInfo.isPinned
+ floatingBtn.imageTintList = style.windowingButtonColor
+ desktopBtn.isSelected = taskInfo.isFreeform
+ desktopBtn.imageTintList = style.windowingButtonColor
+ }
+
+ private fun bindMoreActionsPill(style: MenuStyle) {
+ moreActionsPill.apply {
+ isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
+ }
+ screenshotBtn.apply {
+ isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
+ background.colorFilter =
+ BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY
)
- ),
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ newWindowBtn.apply {
+ isGone = !shouldShowNewWindowButton
+ background.colorFilter =
+ BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ }
+
+ private fun bindOpenInBrowserPill(style: MenuStyle) {
+ openInBrowserPill.apply {
+ isGone = !shouldShowBrowserPill
+ background.colorFilter = BlendModeColorFilter(
+ style.backgroundColor, BlendMode.MULTIPLY
+ )
+ }
+
+ browserBtn.apply {
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
+ }
+
+ private data class MenuStyle(
+ @ColorInt val backgroundColor: Int,
+ @ColorInt val textColor: Int,
+ val windowingButtonColor: ColorStateList,
)
}
- private data class MenuStyle(
- @ColorInt val backgroundColor: Int,
- @ColorInt val textColor: Int,
- val windowingButtonColor: ColorStateList,
- )
-
companion object {
private const val TAG = "HandleMenu"
private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
@@ -530,11 +636,8 @@
fun create(
parentDecor: DesktopModeWindowDecoration,
layoutResId: Int,
- onClickListener: View.OnClickListener?,
- onTouchListener: View.OnTouchListener?,
appIconBitmap: Bitmap?,
appName: CharSequence?,
- displayController: DisplayController,
splitScreenController: SplitScreenController,
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
@@ -550,11 +653,8 @@
override fun create(
parentDecor: DesktopModeWindowDecoration,
layoutResId: Int,
- onClickListener: View.OnClickListener?,
- onTouchListener: View.OnTouchListener?,
appIconBitmap: Bitmap?,
appName: CharSequence?,
- displayController: DisplayController,
splitScreenController: SplitScreenController,
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
@@ -566,11 +666,8 @@
return HandleMenu(
parentDecor,
layoutResId,
- onClickListener,
- onTouchListener,
appIconBitmap,
appName,
- displayController,
splitScreenController,
shouldShowWindowingPill,
shouldShowNewWindowButton,
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 aa2ce0f..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,13 +60,13 @@
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
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_12
import com.android.wm.shell.windowdecor.common.OPACITY_40
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import com.android.wm.shell.windowdecor.common.withAlpha
import java.util.function.Supplier
@@ -102,15 +102,15 @@
/** Creates and shows the maximize window. */
fun show(
- onMaximizeClickListener: OnTaskActionClickListener,
- onLeftSnapClickListener: OnTaskActionClickListener,
- onRightSnapClickListener: OnTaskActionClickListener,
+ onMaximizeOrRestoreClickListener: () -> Unit,
+ onLeftSnapClickListener: () -> Unit,
+ onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
onOutsideTouchListener: () -> Unit,
) {
if (maximizeMenu != null) return
createMaximizeMenu(
- onMaximizeClickListener = onMaximizeClickListener,
+ onMaximizeClickListener = onMaximizeOrRestoreClickListener,
onLeftSnapClickListener = onLeftSnapClickListener,
onRightSnapClickListener = onRightSnapClickListener,
onHoverListener = onHoverListener,
@@ -121,17 +121,18 @@
/** Closes the maximize window and releases its view. */
fun close() {
- maximizeMenuView?.cancelAnimation()
- maximizeMenu?.releaseView()
+ maximizeMenuView?.animateCloseMenu {
+ maximizeMenu?.releaseView()
+ }
maximizeMenu = null
maximizeMenuView = null
}
/** Create a maximize menu that is attached to the display area. */
private fun createMaximizeMenu(
- onMaximizeClickListener: OnTaskActionClickListener,
- onLeftSnapClickListener: OnTaskActionClickListener,
- onRightSnapClickListener: OnTaskActionClickListener,
+ onMaximizeClickListener: () -> Unit,
+ onLeftSnapClickListener: () -> Unit,
+ onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
onOutsideTouchListener: () -> Unit
) {
@@ -147,7 +148,8 @@
menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSPARENT
)
lp.title = "Maximize Menu for Task=" + taskInfo.taskId
@@ -165,17 +167,10 @@
menuHeight = menuHeight,
menuPadding = menuPadding,
).also { menuView ->
- val taskId = taskInfo.taskId
menuView.bind(taskInfo)
- menuView.onMaximizeClickListener = {
- onMaximizeClickListener.onClick(taskId, "maximize_menu_option")
- }
- menuView.onLeftSnapClickListener = {
- onLeftSnapClickListener.onClick(taskId, "left_snap_option")
- }
- menuView.onRightSnapClickListener = {
- onRightSnapClickListener.onClick(taskId, "right_snap_option")
- }
+ menuView.onMaximizeClickListener = onMaximizeClickListener
+ menuView.onLeftSnapClickListener = onLeftSnapClickListener
+ menuView.onRightSnapClickListener = onRightSnapClickListener
menuView.onMenuHoverListener = onHoverListener
menuView.onOutsideTouchListener = onOutsideTouchListener
viewHost.setView(menuView.rootView, lp)
@@ -262,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
@@ -353,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
@@ -374,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
@@ -388,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",
@@ -398,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
@@ -410,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. */
@@ -652,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/OnTaskRepositionAnimationListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskRepositionAnimationListener.kt
new file mode 100644
index 0000000..214a6fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskRepositionAnimationListener.kt
@@ -0,0 +1,32 @@
+/*
+ * 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
+
+/**
+ * Listener that allows notifies when an animation that is repositioning a task is starting
+ * and finishing the animation.
+ */
+interface OnTaskRepositionAnimationListener {
+ /**
+ * Notifies that an animation is about to be started.
+ */
+ fun onAnimationStart(taskId: Int)
+
+ /**
+ * Notifies that an animation is about to be finished.
+ */
+ fun onAnimationEnd(taskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt
index cd2dac8..fd6c4d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt
@@ -32,6 +32,7 @@
import android.view.SurfaceControlViewHost
import android.view.SurfaceSession
import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL
import android.view.WindowlessWindowManager
import android.widget.ImageView
import android.window.TaskConstants
@@ -151,6 +152,7 @@
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT)
lp.title = "Resize veil icon window of Task=" + taskInfo.taskId
+ lp.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL
lp.setTrustedOverlay()
val wwm = WindowlessWindowManager(taskInfo.configuration,
iconSurface, null /* hostInputToken */)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index b5b476d..7f2c1a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -163,14 +163,7 @@
} else {
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
- if (!mTaskBoundsAtDragStart.equals(mRepositionTaskBounds)) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
- // Drag-move ended where it originally started, no need to update WM.
- mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW);
- }
+ mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_DRAG_WINDOW);
}
mCtrlType = CTRL_TYPE_UNDEFINED;
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 6828560..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
@@ -22,6 +22,8 @@
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -59,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;
@@ -141,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();
@@ -182,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());
}
/**
@@ -232,14 +241,15 @@
}
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();
outResult.mHeight = taskBounds.height();
outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
final Resources resources = mDecorWindowContext.getResources();
- outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId)
+ + params.mCaptionTopPadding;
outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
@@ -281,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) {
@@ -443,9 +456,12 @@
}
mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
+ new WindowManager.LayoutParams(
+ outResult.mCaptionWidth,
+ outResult.mCaptionHeight,
TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
lp.inputFeatures = params.mInputFeatures;
@@ -459,6 +475,7 @@
}
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
+ outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
mViewHost.setView(outResult.mRootView, lp);
Trace.endSection();
} else {
@@ -469,6 +486,7 @@
}
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
+ outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
mViewHost.relayout(lp);
Trace.endSection();
}
@@ -489,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) {
@@ -613,6 +640,49 @@
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
*
+ * @param v View to attach to the window
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return the {@link AdditionalViewHostViewContainer} that was added.
+ */
+ AdditionalViewHostViewContainer addWindow(@NonNull View v, @NonNull String namePrefix,
+ @NonNull SurfaceControl.Transaction t, @NonNull SurfaceSyncGroup ssg,
+ int xPos, int yPos, int width, int height) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ SurfaceControl windowSurfaceControl = builder
+ .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mDecorationContainerSurface)
+ .setCallsite("WindowDecoration.addWindow")
+ .build();
+ t.setPosition(windowSurfaceControl, xPos, yPos)
+ .setWindowCrop(windowSurfaceControl, width, height)
+ .show(windowSurfaceControl);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(
+ width,
+ height,
+ TYPE_APPLICATION,
+ FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSPARENT);
+ lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+ windowSurfaceControl, null /* hostInputToken */);
+ SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+ .create(mDecorWindowContext, mDisplay, windowManager);
+ ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
+ return new AdditionalViewHostViewContainer(windowSurfaceControl, viewHost,
+ mSurfaceControlTransactionSupplier);
+ }
+
+ /**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ *
* @param layoutId layout to make the window from
* @param t the transaction to apply
* @param xPos x position of new window
@@ -624,32 +694,8 @@
AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
int width, int height) {
- final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
- SurfaceControl windowSurfaceControl = builder
- .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
- .setContainerLayer()
- .setParent(mDecorationContainerSurface)
- .setCallsite("WindowDecoration.addWindow")
- .build();
- View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
-
- t.setPosition(windowSurfaceControl, xPos, yPos)
- .setWindowCrop(windowSurfaceControl, width, height)
- .show(windowSurfaceControl);
- final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(width, height, TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | FLAG_WATCH_OUTSIDE_TOUCH,
- PixelFormat.TRANSPARENT);
- lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
- lp.setTrustedOverlay();
- WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
- windowSurfaceControl, null /* hostInputToken */);
- SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
- .create(mDecorWindowContext, mDisplay, windowManager);
- ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
- return new AdditionalViewHostViewContainer(windowSurfaceControl, viewHost,
- mSurfaceControlTransactionSupplier);
+ final View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+ return addWindow(v, namePrefix, t, ssg, xPos, yPos, width, height);
}
/**
@@ -683,6 +729,8 @@
int mShadowRadiusId;
int mCornerRadius;
+ int mCaptionTopPadding;
+
Configuration mWindowDecorConfig;
boolean mApplyStartTransactionOnDraw;
@@ -699,6 +747,8 @@
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
+ mCaptionTopPadding = 0;
+
mApplyStartTransactionOnDraw = false;
mSetTaskPositionAndCrop = false;
mWindowDecorConfig = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index f1370bb..cadd80e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.additionalviewcontainer
+import android.annotation.LayoutRes
import android.content.Context
import android.graphics.PixelFormat
import android.view.Gravity
@@ -29,24 +30,52 @@
* for view containers that should be above the status bar layer.
*/
class AdditionalSystemViewContainer(
- private val context: Context,
+ context: Context,
taskId: Int,
x: Int,
y: Int,
width: Int,
height: Int,
flags: Int,
- layoutId: Int? = null
-) : AdditionalViewContainer() {
override val view: View
+) : AdditionalViewContainer() {
+
+ constructor(
+ context: Context,
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ @LayoutRes layoutId: Int
+ ) : this(
+ context = context,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
+ )
+
+ constructor(
+ context: Context, taskId: Int, x: Int, y: Int, width: Int, height: Int, flags: Int
+ ) : this(
+ context = context,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = View(context)
+ )
+
val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
init {
- if (layoutId != null) {
- view = LayoutInflater.from(context).inflate(layoutId, null)
- } else {
- view = View(context)
- }
val lp = WindowManager.LayoutParams(
width, height, x, y,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
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/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index 17b3dea..d0eb6da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -238,12 +238,12 @@
override fun onHandleMenuClosed() {}
- fun setAnimatingTaskResize(animatingTaskResize: Boolean) {
- // If animating a task resize, cancel any running hover animations
- if (animatingTaskResize) {
+ fun setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition: Boolean) {
+ // If animating a task resize or reposition, cancel any running hover animations
+ if (animatingTaskResizeOrReposition) {
maximizeButtonView.cancelHoverAnimation()
}
- maximizeButtonView.hoverDisabled = animatingTaskResize
+ maximizeButtonView.hoverDisabled = animatingTaskResizeOrReposition
}
fun onMaximizeWindowHoverExit() {
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index a77fd51..d1be12f 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 1157642
+# Bug component: 928594
# includes OWNERS from parent directories
natanieljr@google.com
pablogamito@google.com
@@ -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/libs/WindowManager/Shell/tests/flicker/OWNERS b/libs/WindowManager/Shell/tests/flicker/OWNERS
new file mode 100644
index 0000000..4db0bab
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1157642
+# includes OWNERS from parent directories
+include platform/development:/tools/winscope/OWNERS
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt
new file mode 100644
index 0000000..9ba3a45
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [CloseAllAppsWithAppHeaderExit]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class CloseAllAppsWithAppHeaderExitTest : CloseAllAppsWithAppHeaderExit() {
+
+ @Test
+ override fun closeAllAppsInDesktop() {
+ super.closeAllAppsInDesktop()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt
new file mode 100644
index 0000000..ed1d488
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowMultiWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [DragAppWindowMultiWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowMultiWindowTest : DragAppWindowMultiWindow()
+{
+ @Test
+ override fun dragAppWindow() {
+ super.dragAppWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt
new file mode 100644
index 0000000..d8b9348
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowSingleWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [DragAppWindowSingleWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowSingleWindowTest : DragAppWindowSingleWindow()
+{
+ @Test
+ override fun dragAppWindow() {
+ super.dragAppWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt
new file mode 100644
index 0000000..546ce2d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.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.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithAppHandleMenu
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [EnterDesktopWithAppHandleMenu]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithAppHandleMenuTest : EnterDesktopWithAppHandleMenu() {
+ @Test
+ override fun enterDesktopWithAppHandleMenu() {
+ super.enterDesktopWithAppHandleMenu()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt
new file mode 100644
index 0000000..b5fdb16
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt
@@ -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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.Rotation
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [EnterDesktopWithDrag]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithDragTest : EnterDesktopWithDrag(Rotation.ROTATION_0) {
+
+ @Test
+ override fun enterDesktopWithDrag() {
+ super.enterDesktopWithDrag()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt
new file mode 100644
index 0000000..8f802d2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt
@@ -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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.Rotation
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ExitDesktopWithDragToTopDragZone
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [ExitDesktopWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ExitDesktopWithDragToTopDragZoneTest :
+ ExitDesktopWithDragToTopDragZone(Rotation.ROTATION_0) {
+ @Test
+ override fun exitDesktopWithDragToTopDragZone() {
+ super.exitDesktopWithDragToTopDragZone()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt
new file mode 100644
index 0000000..f899082
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [MaximizeAppWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class MaximizeAppWindowTest : MaximizeAppWindow()
+{
+ @Test
+ override fun maximizeAppWindow() {
+ super.maximizeAppWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt
new file mode 100644
index 0000000..63c428a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.MinimizeWindowOnAppOpen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [MinimizeWindowOnAppOpen]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class MinimizeWindowOnAppOpenTest : MinimizeWindowOnAppOpen()
+{
+ @Test
+ override fun openAppToMinimizeWindow() {
+ // Launch a new app while 4 apps are already open on desktop. This should result in the
+ // first app we opened to be minimized.
+ super.openAppToMinimizeWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt
new file mode 100644
index 0000000..4797aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.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.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.Rotation
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [ResizeAppWithCornerResize]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize(Rotation.ROTATION_0) {
+ @Test
+ override fun resizeAppWithCornerResize() {
+ super.resizeAppWithCornerResize()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt
new file mode 100644
index 0000000..9a71361
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.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.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.SwitchToOverviewFromDesktop
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [SwitchToOverviewFromDesktop]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop() {
+ @Test
+ override fun switchToOverview() {
+ super.switchToOverview()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
new file mode 100644
index 0000000..079ee13
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/res/layout/caption_layout.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+<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/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 716a148..413e495 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -365,7 +365,7 @@
final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 12,
WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.appCompatTaskInfo.topActivityInSizeCompat = false;
+ taskInfo1.appCompatTaskInfo.setTopActivityInSizeCompat(false);
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
@@ -378,7 +378,7 @@
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true;
+ taskInfo2.appCompatTaskInfo.setTopActivityInSizeCompat(true);
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
@@ -388,7 +388,7 @@
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true;
+ taskInfo3.appCompatTaskInfo.setTopActivityInSizeCompat(true);
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
@@ -403,7 +403,7 @@
final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 12,
WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = false;
+ taskInfo1.appCompatTaskInfo.setEligibleForLetterboxEducation(false);
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
@@ -418,7 +418,7 @@
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
+ taskInfo2.appCompatTaskInfo.setEligibleForLetterboxEducation(true);
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
@@ -428,7 +428,7 @@
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
+ taskInfo3.appCompatTaskInfo.setEligibleForLetterboxEducation(true);
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 57e469d..56fad95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -68,6 +68,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -114,6 +115,8 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
+ private Transitions mTransitions;
+ @Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private BackAnimationController mController;
@@ -156,7 +159,8 @@
mContentResolver,
mAnimationBackground,
mShellBackAnimationRegistry,
- mShellCommandHandler);
+ mShellCommandHandler,
+ mTransitions);
mShellInit.init();
mShellExecutor.flushAll();
mTouchableRegion = new Rect(0, 0, 100, 100);
@@ -316,7 +320,8 @@
mContentResolver,
mAnimationBackground,
mShellBackAnimationRegistry,
- mShellCommandHandler);
+ mShellCommandHandler,
+ mTransitions);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 764d5a9..654d7a8e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -119,7 +119,7 @@
@Test
public void reappliesVisibilityToChangedLeash() {
verifyZeroInteractions(mT);
- mPerDisplay.mImeShowing = true;
+ mPerDisplay.mImeShowing = false;
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
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 77e22cd..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) {
@@ -681,7 +687,7 @@
@Test
public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
- taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
+ taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
@@ -705,12 +711,12 @@
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.displayId = displayId;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.isVisible = isVisible;
taskInfo.isFocused = isFocused;
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
- taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true;
- taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true;
+ taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(true);
+ taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(true);
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 3b93861..e5d1919 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -146,7 +146,7 @@
private static TaskInfo createTaskInfo(boolean hasSizeCompat) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
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/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index c5033f3..1c01756 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -446,7 +446,7 @@
private static TaskInfo createTaskInfo(boolean hasSizeCompat) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
// Letterboxed activity that takes half the screen should show size compat restart button
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
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 b5664ac..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();
@@ -499,7 +526,7 @@
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.userId = userId;
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = eligible;
+ taskInfo.appCompatTaskInfo.setEligibleForLetterboxEducation(eligible);
taskInfo.configuration.windowConfiguration.setBounds(bounds);
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 7a64196..e8e68bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -155,7 +155,7 @@
private static TaskInfo createTaskInfo(boolean hasSizeCompat) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 9f288cc..9f86d49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -317,7 +317,7 @@
// layout should be inflated
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableBounds.height();
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width();
- taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled = true;
+ taskInfo.appCompatTaskInfo.setUserFullscreenOverrideEnabled(true);
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
@@ -482,9 +482,9 @@
boolean topActivityBoundsLetterboxed, String action, String category) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
- eligibleForUserAspectRatioButton;
- taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+ taskInfo.appCompatTaskInfo.setEligibleForUserAspectRatioButton(
+ eligibleForUserAspectRatioButton);
+ taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(topActivityBoundsLetterboxed);
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
taskInfo.baseIntent = new Intent(action).addCategory(category);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt
new file mode 100644
index 0000000..43bd412
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.impl
+
+import com.android.wm.shell.compatui.api.CompatUIComponentState
+import com.android.wm.shell.compatui.api.CompatUISpec
+import com.android.wm.shell.compatui.api.CompatUIState
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
+
+/**
+ * Asserts no component state exists for the given CompatUISpec
+ */
+internal fun CompatUIState.assertHasNoStateFor(componentId: String) =
+ assertNull(stateForComponent(componentId))
+
+/**
+ * Asserts component state for the given CompatUISpec
+ */
+internal fun CompatUIState.assertHasStateEqualsTo(
+ componentId: String,
+ expected: CompatUIComponentState
+) =
+ assertEquals(stateForComponent(componentId), expected)
+
+/**
+ * Asserts no component exists for the given CompatUISpec
+ */
+internal fun CompatUIState.assertHasNoComponentFor(componentId: String) =
+ assertNull(getUIComponent(componentId))
+
+/**
+ * Asserts component for the given CompatUISpec
+ */
+internal fun CompatUIState.assertHasComponentFor(componentId: String) =
+ assertNotNull(getUIComponent(componentId))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt
new file mode 100644
index 0000000..8136074
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.impl
+
+import android.app.ActivityManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUIComponentState
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUIState
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link DefaultCompatUIHandler}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultCompatUIHandlerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultCompatUIHandlerTest {
+
+ lateinit var compatUIRepository: FakeCompatUIRepository
+ lateinit var compatUIHandler: DefaultCompatUIHandler
+ lateinit var compatUIState: CompatUIState
+ lateinit var fakeIdGenerator: FakeCompatUIComponentIdGenerator
+
+ @Before
+ fun setUp() {
+ compatUIRepository = FakeCompatUIRepository()
+ compatUIState = CompatUIState()
+ fakeIdGenerator = FakeCompatUIComponentIdGenerator("compId")
+ compatUIHandler = DefaultCompatUIHandler(compatUIRepository, compatUIState,
+ fakeIdGenerator)
+ }
+
+ @Test
+ fun `when creationReturn is false no state is stored`() {
+ // We add a spec to the repository
+ val fakeLifecycle = FakeCompatUILifecyclePredicates(
+ creationReturn = false,
+ removalReturn = false
+ )
+ val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+ compatUIRepository.addSpec(fakeCompatUISpec)
+
+ val generatedId = fakeIdGenerator.generatedComponentId
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeIdGenerator.assertGenerateInvocations(1)
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(0)
+ fakeLifecycle.assertInitialStateInvocation(0)
+ compatUIState.assertHasNoStateFor(generatedId)
+ compatUIState.assertHasNoComponentFor(generatedId)
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+ fakeLifecycle.assertCreationInvocation(2)
+ fakeLifecycle.assertRemovalInvocation(0)
+ fakeLifecycle.assertInitialStateInvocation(0)
+ compatUIState.assertHasNoStateFor(generatedId)
+ compatUIState.assertHasNoComponentFor(generatedId)
+ }
+
+ @Test
+ fun `when creationReturn is true and no state is created no state is stored`() {
+ // We add a spec to the repository
+ val fakeLifecycle = FakeCompatUILifecyclePredicates(
+ creationReturn = true,
+ removalReturn = false
+ )
+ val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+ compatUIRepository.addSpec(fakeCompatUISpec)
+
+ val generatedId = fakeIdGenerator.generatedComponentId
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(0)
+ fakeLifecycle.assertInitialStateInvocation(1)
+ compatUIState.assertHasNoStateFor(generatedId)
+ compatUIState.assertHasComponentFor(generatedId)
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(1)
+ fakeLifecycle.assertInitialStateInvocation(1)
+ compatUIState.assertHasNoStateFor(generatedId)
+ compatUIState.assertHasComponentFor(generatedId)
+ }
+
+ @Test
+ fun `when creationReturn is true and state is created state is stored`() {
+ val fakeComponentState = object : CompatUIComponentState {}
+ // We add a spec to the repository
+ val fakeLifecycle = FakeCompatUILifecyclePredicates(
+ creationReturn = true,
+ removalReturn = false,
+ initialState = { _, _ -> fakeComponentState }
+ )
+ val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+ compatUIRepository.addSpec(fakeCompatUISpec)
+
+ val generatedId = fakeIdGenerator.generatedComponentId
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(0)
+ fakeLifecycle.assertInitialStateInvocation(1)
+ compatUIState.assertHasStateEqualsTo(generatedId, fakeComponentState)
+ compatUIState.assertHasComponentFor(generatedId)
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(1)
+ fakeLifecycle.assertInitialStateInvocation(1)
+ compatUIState.assertHasStateEqualsTo(generatedId, fakeComponentState)
+ compatUIState.assertHasComponentFor(generatedId)
+ }
+
+ @Test
+ fun `when lifecycle is complete and state is created state is stored and removed`() {
+ val fakeComponentState = object : CompatUIComponentState {}
+ // We add a spec to the repository
+ val fakeLifecycle = FakeCompatUILifecyclePredicates(
+ creationReturn = true,
+ removalReturn = true,
+ initialState = { _, _ -> fakeComponentState }
+ )
+ val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+ compatUIRepository.addSpec(fakeCompatUISpec)
+
+ val generatedId = fakeIdGenerator.generatedComponentId
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(0)
+ fakeLifecycle.assertInitialStateInvocation(1)
+ compatUIState.assertHasStateEqualsTo(generatedId, fakeComponentState)
+ compatUIState.assertHasComponentFor(generatedId)
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+
+ fakeLifecycle.assertCreationInvocation(1)
+ fakeLifecycle.assertRemovalInvocation(1)
+ fakeLifecycle.assertInitialStateInvocation(1)
+ compatUIState.assertHasNoStateFor(generatedId)
+ compatUIState.assertHasNoComponentFor(generatedId)
+ }
+
+ @Test
+ fun `idGenerator is invoked every time a component is created`() {
+ // We add a spec to the repository
+ val fakeLifecycle = FakeCompatUILifecyclePredicates(
+ creationReturn = true,
+ removalReturn = true,
+ )
+ val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+ compatUIRepository.addSpec(fakeCompatUISpec)
+ // Component creation
+ fakeIdGenerator.assertGenerateInvocations(0)
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+ fakeIdGenerator.assertGenerateInvocations(1)
+
+ compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+ fakeIdGenerator.assertGenerateInvocations(2)
+ }
+
+ private fun testCompatUIInfo(): CompatUIInfo {
+ val taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = 1
+ return CompatUIInfo(taskInfo, null)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
index 1a86cfd..e35acb2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -19,6 +19,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUILifecyclePredicates
import com.android.wm.shell.compatui.api.CompatUIRepository
import com.android.wm.shell.compatui.api.CompatUISpec
import org.junit.Assert.assertEquals
@@ -50,16 +51,16 @@
@Test(expected = IllegalStateException::class)
fun `addSpec throws exception with specs with duplicate id`() {
- repository.addSpec(CompatUISpec("one"))
- repository.addSpec(CompatUISpec("one"))
+ repository.addSpec(specById("one"))
+ repository.addSpec(specById("one"))
}
@Test
fun `iterateOn invokes the consumer`() {
with(repository) {
- addSpec(CompatUISpec("one"))
- addSpec(CompatUISpec("two"))
- addSpec(CompatUISpec("three"))
+ addSpec(specById("one"))
+ addSpec(specById("two"))
+ addSpec(specById("three"))
val consumer = object : (CompatUISpec) -> Unit {
var acc = ""
override fun invoke(spec: CompatUISpec) {
@@ -74,9 +75,9 @@
@Test
fun `findSpec returns existing specs`() {
with(repository) {
- val one = CompatUISpec("one")
- val two = CompatUISpec("two")
- val three = CompatUISpec("three")
+ val one = specById("one")
+ val two = specById("two")
+ val three = specById("three")
addSpec(one)
addSpec(two)
addSpec(three)
@@ -86,4 +87,10 @@
assertNull(findSpec("abc"))
}
}
+
+ private fun specById(name: String): CompatUISpec =
+ CompatUISpec(name = name, lifecycle = CompatUILifecyclePredicates(
+ creationPredicate = { _, _ -> true },
+ removalPredicate = { _, _, _ -> true }
+ ))
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIComponentIdGenerator.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIComponentIdGenerator.kt
new file mode 100644
index 0000000..bc743ed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIComponentIdGenerator.kt
@@ -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.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUISpec
+import junit.framework.Assert.assertEquals
+
+/**
+ * A Fake {@link CompatUIComponentIdGenerator} implementation.
+ */
+class FakeCompatUIComponentIdGenerator(var generatedComponentId: String = "compId") :
+ CompatUIComponentIdGenerator {
+
+ var generateInvocations = 0
+
+ override fun generateId(compatUIInfo: CompatUIInfo, spec: CompatUISpec): String {
+ generateInvocations++
+ return generatedComponentId
+ }
+
+ fun resetInvocations() {
+ generateInvocations = 0
+ }
+
+ fun assertGenerateInvocations(expected: Int) =
+ assertEquals(expected, generateInvocations)
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt
new file mode 100644
index 0000000..bbaa2db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.impl
+
+import com.android.wm.shell.compatui.api.CompatUIComponentState
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUILifecyclePredicates
+import com.android.wm.shell.compatui.api.CompatUISharedState
+import junit.framework.Assert.assertEquals
+
+/**
+ * Fake class for {@link CompatUILifecycle}
+ */
+class FakeCompatUILifecyclePredicates(
+ private val creationReturn: Boolean,
+ private val removalReturn: Boolean,
+ private val initialState: (
+ CompatUIInfo,
+ CompatUISharedState
+ ) -> CompatUIComponentState? = { _, _ -> null }
+) {
+ var creationInvocation = 0
+ var removalInvocation = 0
+ var initialStateInvocation = 0
+ var lastCreationCompatUIInfo: CompatUIInfo? = null
+ var lastCreationSharedState: CompatUISharedState? = null
+ var lastRemovalCompatUIInfo: CompatUIInfo? = null
+ var lastRemovalSharedState: CompatUISharedState? = null
+ var lastRemovalCompState: CompatUIComponentState? = null
+ fun getLifecycle() = CompatUILifecyclePredicates(
+ creationPredicate = { uiInfo, sharedState ->
+ lastCreationCompatUIInfo = uiInfo
+ lastCreationSharedState = sharedState
+ creationInvocation++
+ creationReturn
+ },
+ removalPredicate = { uiInfo, sharedState, compState ->
+ lastRemovalCompatUIInfo = uiInfo
+ lastRemovalSharedState = sharedState
+ lastRemovalCompState = compState
+ removalInvocation++
+ removalReturn
+ },
+ stateBuilder = { a, b -> initialStateInvocation++; initialState(a, b) }
+ )
+
+ fun assertCreationInvocation(expected: Int) =
+ assertEquals(expected, creationInvocation)
+
+ fun assertRemovalInvocation(expected: Int) =
+ assertEquals(expected, removalInvocation)
+
+ fun assertInitialStateInvocation(expected: Int) =
+ assertEquals(expected, initialStateInvocation)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
copy to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt
index 1f04a44..1ecd52e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt
@@ -14,12 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.activatable
+package com.android.wm.shell.compatui.impl
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
+import com.android.wm.shell.compatui.api.CompatUISpec
-/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+/**
+ * Fake implementation for {@link ompatUISpec}
+ */
+class FakeCompatUISpec(
+ val name: String,
+ val lifecycle: FakeCompatUILifecyclePredicates
+) {
+ fun getSpec(): CompatUISpec = CompatUISpec(
+ name = name,
+ lifecycle = lifecycle.getLifecycle()
+ )
}
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 c3d31ba..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
@@ -85,6 +85,7 @@
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.common.split.SplitScreenConstants
+import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -117,7 +118,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
@@ -128,10 +128,11 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.eq
import org.mockito.kotlin.capture
+import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
/**
@@ -156,6 +157,7 @@
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
@Mock lateinit var keyguardManager: KeyguardManager
+ @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock
@@ -249,6 +251,7 @@
dragAndDropController,
transitions,
keyguardManager,
+ mReturnToDragStartAnimator,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
@@ -728,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()
@@ -748,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)!!
@@ -1302,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
@@ -1566,6 +1694,21 @@
}
@Test
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onAnimationStateChanged(true)
+
+ // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+ assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
@@ -2359,10 +2502,12 @@
controller.onDragPositioningEnd(
task,
+ mockSurface,
Point(100, -100), /* position */
PointF(200f, -200f), /* inputCoordinate */
- Rect(100, -100, 500, 1000), /* taskBounds */
- Rect(0, 50, 2000, 2000) /* validDragArea */)
+ Rect(100, -100, 500, 1000), /* currentDragBounds */
+ Rect(0, 50, 2000, 2000), /* validDragArea */
+ Rect() /* dragStartBounds */ )
val rectAfterEnd = Rect(100, 50, 500, 1150)
verify(transitions)
.startTransition(
@@ -2376,6 +2521,42 @@
}
@Test
+ fun onDesktopDragEnd_noIndicator_updatesTaskBounds() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+
+ val currentDragBounds = Rect(100, 200, 500, 1000)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 200), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ currentDragBounds, /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */)
+
+
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ change.configuration.windowConfiguration.bounds == currentDragBounds
+ }
+ },
+ eq(null))
+ }
+
+ @Test
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -2460,6 +2641,63 @@
}
@Test
+ fun getSnapBounds_calculatesBoundsForResizable() {
+ val bounds = Rect(100, 100, 300, 300)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo = ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = true
+ }
+
+ val currentDragBounds = Rect(0, 100, 200, 300)
+ val expectedBounds = Rect(
+ STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
+ )
+
+ controller.snapToHalfScreen(task, currentDragBounds, SnapPosition.LEFT)
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun handleSnapResizingTask_nonResizable_snapsToHalfScreen() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply {
+ isResizeable = false
+ }
+ val preDragBounds = Rect(100, 100, 400, 500)
+ val currentDragBounds = Rect(0, 100, 300, 500)
+
+ controller.handleSnapResizingTask(
+ task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds)
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(
+ Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun handleSnapResizingTask_nonResizable_startsRepositionAnimation() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply {
+ isResizeable = false
+ }
+ val preDragBounds = Rect(100, 100, 400, 500)
+ val currentDragBounds = Rect(0, 100, 300, 500)
+
+ controller.handleSnapResizingTask(
+ task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds)
+ verify(mReturnToDragStartAnimator).start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(preDragBounds)
+ )
+ }
+
+ @Test
fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
val bounds = Rect(0, 0, 200, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
@@ -2599,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()
@@ -2616,23 +2856,23 @@
isResizeable = isResizable
configuration.orientation = deviceOrientation
configuration.windowConfiguration.windowingMode = windowingMode
+ appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
+ appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
if (shouldLetterbox) {
if (deviceOrientation == ORIENTATION_LANDSCAPE &&
screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
// Letterbox to portrait size
- appCompatTaskInfo.topActivityBoundsLetterboxed = true
- appCompatTaskInfo.topActivityLetterboxWidth = 1200
- appCompatTaskInfo.topActivityLetterboxHeight = 1600
+ appCompatTaskInfo.setTopActivityLetterboxed(true)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = 1200
+ appCompatTaskInfo.topActivityLetterboxAppHeight = 1600
} else if (deviceOrientation == ORIENTATION_PORTRAIT &&
screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
// Letterbox to landscape size
- appCompatTaskInfo.topActivityBoundsLetterboxed = true
- appCompatTaskInfo.topActivityLetterboxWidth = 1600
- appCompatTaskInfo.topActivityLetterboxHeight = 1200
+ appCompatTaskInfo.setTopActivityLetterboxed(true)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = 1600
+ appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
}
- } else {
- appCompatTaskInfo.topActivityBoundsLetterboxed = false
}
if (deviceOrientation == ORIENTATION_LANDSCAPE) {
@@ -2705,11 +2945,14 @@
return arg.value
}
- private fun getLatestToggleResizeDesktopTaskWct(): WindowContainerTransaction {
+ private fun getLatestToggleResizeDesktopTaskWct(
+ currentBounds: Rect? = null
+ ): WindowContainerTransaction {
val arg: ArgumentCaptor<WindowContainerTransaction> =
ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce()).startTransition(capture(arg))
+ verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+ .startTransition(capture(arg), eq(currentBounds))
} else {
verify(shellTaskOrganizer).applyTransaction(capture(arg))
}
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/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index eaef704..ff6c7ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -245,38 +245,6 @@
}
@Test
- public void testExitSplitScreen() {
- when(mMainStage.isActive()).thenReturn(true);
- mStageCoordinator.exitSplitScreen(INVALID_TASK_ID, EXIT_REASON_RETURN_HOME);
- verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false));
- verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
- }
-
- @Test
- public void testExitSplitScreenToMainStage() {
- when(mMainStage.isActive()).thenReturn(true);
- final int testTaskId = 12345;
- when(mMainStage.containsTask(eq(testTaskId))).thenReturn(true);
- when(mSideStage.containsTask(eq(testTaskId))).thenReturn(false);
- mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME);
- verify(mMainStage).reorderChild(eq(testTaskId), eq(true),
- any(WindowContainerTransaction.class));
- verify(mMainStage).resetBounds(any(WindowContainerTransaction.class));
- }
-
- @Test
- public void testExitSplitScreenToSideStage() {
- when(mMainStage.isActive()).thenReturn(true);
- final int testTaskId = 12345;
- when(mMainStage.containsTask(eq(testTaskId))).thenReturn(false);
- when(mSideStage.containsTask(eq(testTaskId))).thenReturn(true);
- mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME);
- verify(mSideStage).reorderChild(eq(testTaskId), eq(true),
- any(WindowContainerTransaction.class));
- verify(mSideStage).resetBounds(any(WindowContainerTransaction.class));
- }
-
- @Test
public void testResolveStartStage_beforeSplitActivated_setsStagePosition() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
@@ -367,12 +335,7 @@
mStageCoordinator.onFinishedWakingUp();
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
- } else {
- verify(mStageCoordinator).onSplitScreenExit();
- verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
- }
+ verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
}
@Test
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/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 261d4b5..d141c2d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -21,6 +21,7 @@
import android.content.ComponentName
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.InsetsState
import android.view.WindowInsetsController
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
@@ -45,7 +46,8 @@
relayoutParams,
taskInfo,
true,
- false
+ false,
+ InsetsState()
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
@@ -63,7 +65,8 @@
relayoutParams,
taskInfo,
true,
- false
+ false,
+ InsetsState()
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
@@ -77,7 +80,8 @@
relayoutParams,
taskInfo,
true,
- false
+ false,
+ InsetsState()
)
Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
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 c3a0bd9..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
@@ -16,27 +16,31 @@
package com.android.wm.shell.windowdecor
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.app.WindowConfiguration.WindowingMode
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
+import android.net.Uri
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
import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
import android.testing.TestableLooper.RunWithLooper
import android.util.SparseArray
import android.view.Choreographer
@@ -47,11 +51,12 @@
import android.view.InsetsSource
import android.view.InsetsState
import android.view.KeyEvent
+import android.view.Surface
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
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
@@ -65,30 +70,36 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser
+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
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
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 com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
+import java.util.Optional
+import java.util.function.Consumer
+import java.util.function.Supplier
+import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentCaptor.forClass
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -97,13 +108,14 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
-import java.util.Optional
-import java.util.function.Supplier
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -147,22 +159,36 @@
@Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
private val bgExecutor = TestShellExecutor()
@Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
+ private lateinit var spyContext: TestableContext
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
}
private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
+ private lateinit var mockitoSession: StaticMockitoSession
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
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .spyStatic(DragPositioningCallbackUtility::class.java)
+ .startMocking()
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
+
+ spyContext = spy(mContext)
+ doNothing().`when`(spyContext).startActivity(any())
shellInit = ShellInit(mockShellExecutor)
windowDecorByTaskIdSpy.clear()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
- mContext,
+ spyContext,
mockShellExecutor,
mockMainHandler,
mockMainChoreographer,
@@ -198,10 +224,25 @@
shellInit.init()
- val listenerCaptor =
+ val displayChangingListenerCaptor =
+ argumentCaptor<DisplayChangeController.OnDisplayChangingListener>()
+ verify(mockDisplayController)
+ .addDisplayChangingController(displayChangingListenerCaptor.capture())
+ displayChangingListener = displayChangingListenerCaptor.firstValue
+ val insetsChangedCaptor =
argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
- verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
- desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
+ verify(displayInsetsController)
+ .addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
+ desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
+ val keyguardChangedCaptor =
+ argumentCaptor<DesktopModeKeyguardChangeListener>()
+ verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
+ desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
}
@Test
@@ -299,7 +340,7 @@
whenever(view.id).thenReturn(R.id.back_button)
val inputManager = mock(InputManager::class.java)
- mContext.addMockSystemService(InputManager::class.java, inputManager)
+ spyContext.addMockSystemService(InputManager::class.java, inputManager)
val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java)
desktopModeWindowDecorViewModel
@@ -315,46 +356,18 @@
}
@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 mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
- isTopActivityTransparent = true
- isTopActivityStyleFloating = true
- numActivities = 1
- }
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply {
+ isTopActivityTransparent = true
+ isTopActivityStyleFloating = true
+ numActivities = 1
}
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@@ -387,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
@@ -466,96 +462,70 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_desktopModeUnsupportedOnDevice_decorNotCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate default enforce device restrictions system property
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- onTaskOpening(task)
- assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate enforce device restrictions system property overridden to false
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ // Simulate enforce device restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- setUpMockDecorationsForTasks(task)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ setUpMockDecorationsForTasks(task)
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
- val mockitoSession: StaticMockitoSession = mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- try {
- // Simulate default enforce device restrictions system property
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
- setUpMockDecorationsForTasks(task)
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ setUpMockDecorationsForTasks(task)
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- } finally {
- mockitoSession.finishMocking()
- }
+ onTaskOpening(task)
+ assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
}
@Test
fun testOnDecorMaximizedOrRestored_togglesTaskSize() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
- return@let captor.value
- }
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+ )
- maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+ maxOrRestoreListenerCaptor.value.invoke()
verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
}
@Test
fun testOnDecorMaximizedOrRestored_closesMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
- return@let captor.value
- }
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+ )
- maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+ maxOrRestoreListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
@@ -563,66 +533,464 @@
@Test
fun testOnDecorSnappedLeft_snapResizes() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnLeftSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
+ onLeftSnapClickListenerCaptor.value.invoke()
- verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT)
+ verify(mockDesktopTasksController)
+ .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
}
@Test
fun testOnDecorSnappedLeft_closeMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnLeftSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onLeftSnapClickListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
}
@Test
+ @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun testOnSnapResizeLeft_nonResizable_decorSnappedLeft() {
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ ).also { it.mTaskInfo.isResizeable = false }
+
+ val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
+ onLeftSnapClickListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController)
+ .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun testOnSnapResizeLeft_nonResizable_decorNotSnapped() {
+ val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onLeftSnapClickListenerCaptor = onLeftSnapClickListenerCaptor
+ ).also { it.mTaskInfo.isResizeable = false }
+
+ val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
+ onLeftSnapClickListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController, never())
+ .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+ }
+
+ @Test
fun testOnDecorSnappedRight_snapResizes() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnRightSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
+ onRightSnapClickListenerCaptor.value.invoke()
- verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT)
+ verify(mockDesktopTasksController)
+ .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
}
@Test
fun testOnDecorSnappedRight_closeMenus() {
- val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
- onTaskOpening(decor.mTaskInfo)
- val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
- .let { captor ->
- verify(decor).setOnRightSnapClickListener(captor.capture())
- return@let captor.value
- }
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ )
- snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+ onRightSnapClickListenerCaptor.value.invoke()
verify(decor).closeHandleMenu()
verify(decor).closeMaximizeMenu()
}
+ @Test
+ @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun testOnSnapResizeRight_nonResizable_decorSnappedRight() {
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ ).also { it.mTaskInfo.isResizeable = false }
+
+ val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
+ onRightSnapClickListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController)
+ .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun testOnSnapResizeRight_nonResizable_decorNotSnapped() {
+ val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onRightSnapClickListenerCaptor = onRightSnapClickListenerCaptor
+ ).also { it.mTaskInfo.isResizeable = false }
+
+ val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
+ onRightSnapClickListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController, never())
+ .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_movesToDesktopWithSource() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(mockDesktopTasksController).moveTaskToDesktop(
+ eq(decor.mTaskInfo.taskId),
+ any(),
+ eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_addsCaptionInsets() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(decor).addCaptionInset(any())
+ }
+
+ @Test
+ fun testDecor_onClickToDesktop_closesHandleMenu() {
+ val toDesktopListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onToDesktopClickListenerCaptor = toDesktopListenerCaptor
+ )
+
+ toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_closesHandleMenu() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_isFreeform_movesToFullscreen() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController).moveToFullscreen(
+ decor.mTaskInfo.taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToFullscreen_isSplit_movesToFullscreen() {
+ val toFullscreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToFullscreenClickListenerCaptor = toFullscreenListenerCaptor
+ )
+
+ toFullscreenListenerCaptor.value.invoke()
+
+ verify(mockSplitScreenController).moveTaskToFullscreen(
+ decor.mTaskInfo.taskId,
+ SplitScreenController.EXIT_REASON_DESKTOP_MODE
+ )
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_closesHandleMenu() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(decor).closeHandleMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_requestsSplit() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(mockDesktopTasksController).requestSplit(decor.mTaskInfo, leftOrTop = false)
+ }
+
+ @Test
+ fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
+ val toSplitScreenListenerCaptor = forClass(Function0::class.java)
+ as ArgumentCaptor<Function0<Unit>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_MULTI_WINDOW,
+ onToSplitScreenClickListenerCaptor = toSplitScreenListenerCaptor
+ )
+
+ toSplitScreenListenerCaptor.value.invoke()
+
+ verify(decor).disposeStatusBarInputLayer()
+ }
+
+ @Test
+ fun testDecor_onClickToOpenBrowser_closeMenus() {
+ val openInBrowserListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<Uri>>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onOpenInBrowserClickListener = openInBrowserListenerCaptor
+ )
+
+ openInBrowserListenerCaptor.value.accept(Uri.EMPTY)
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testDecor_onClickToOpenBrowser_opensBrowser() {
+ doNothing().whenever(spyContext).startActivity(any())
+ val uri = Uri.parse("https://www.google.com")
+ val openInBrowserListenerCaptor = forClass(Consumer::class.java)
+ as ArgumentCaptor<Consumer<Uri>>
+ createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FULLSCREEN,
+ onOpenInBrowserClickListener = openInBrowserListenerCaptor
+ )
+
+ openInBrowserListenerCaptor.value.accept(uri)
+
+ verify(spyContext).startActivity(argThat { intent ->
+ intent.data == uri
+ && ((intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
+ && intent.categories.contains(Intent.CATEGORY_LAUNCHER)
+ && intent.action == Intent.ACTION_MAIN
+ })
+ }
+
+ @Test
+ fun testOnDisplayRotation_tasksOutOfValidArea_taskBoundsUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ doReturn(true).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct).setBounds(eq(task.token), any())
+ verify(wct).setBounds(eq(secondTask.token), any())
+ verify(wct).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_taskInValidArea_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ doReturn(false).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct, never()).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_sameOrientationRotation_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_180, null, wct
+ )
+
+ verify(wct, never()).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_differentDisplayId_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ doReturn(true).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_nonFreeformTask_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FULLSCREEN)
+ val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_PINNED)
+
+ doReturn(true).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ private fun createOpenTaskDecoration(
+ @WindowingMode windowingMode: Int,
+ onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onRightSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onToDesktopClickListenerCaptor: ArgumentCaptor<Consumer<DesktopModeTransitionSource>> =
+ forClass(Consumer::class.java) as ArgumentCaptor<Consumer<DesktopModeTransitionSource>>,
+ onToFullscreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onToSplitScreenClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
+ forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
+ onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Uri>> =
+ forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Uri>>,
+ ): DesktopModeWindowDecoration {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
+ onTaskOpening(decor.mTaskInfo)
+ verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
+ verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
+ verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture())
+ verify(decor).setOnToDesktopClickListener(onToDesktopClickListenerCaptor.capture())
+ verify(decor).setOnToFullscreenClickListener(onToFullscreenClickListenerCaptor.capture())
+ verify(decor).setOnToSplitScreenClickListener(onToSplitScreenClickListenerCaptor.capture())
+ verify(decor).setOpenInBrowserClickListener(onOpenInBrowserClickListener.capture())
+ return decor
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
@@ -643,7 +1011,7 @@
private fun createTask(
displayId: Int = DEFAULT_DISPLAY,
- @WindowConfiguration.WindowingMode windowingMode: Int,
+ @WindowingMode windowingMode: Int,
activityType: Int = ACTIVITY_TYPE_STANDARD,
focused: Boolean = true,
activityInfo: ActivityInfo = ActivityInfo(),
@@ -656,6 +1024,7 @@
.build().apply {
topActivityInfo = activityInfo
isFocused = focused
+ isResizeable = true
}
}
@@ -668,6 +1037,11 @@
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
+ if (task.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
+ .thenReturn(true)
+ }
+ whenever(decoration.calculateValidDragArea()).thenReturn(Rect(0, 60, 2560, 1600))
return decoration
}
@@ -688,7 +1062,7 @@
)
}
- private fun RunningTaskInfo.setWindowingMode(@WindowConfiguration.WindowingMode mode: Int) {
+ private fun RunningTaskInfo.setWindowingMode(@WindowingMode mode: Int) {
configuration.windowConfiguration.windowingMode = mode
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index e60eb77..596adfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -95,9 +95,9 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
-import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.junit.After;
@@ -111,6 +111,7 @@
import org.mockito.Mock;
import org.mockito.quality.Strictness;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -168,7 +169,7 @@
@Mock
private Handler mMockHandler;
@Mock
- private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+ private Consumer<Uri> mMockOpenInBrowserClickListener;
@Mock
private AppToWebGenericLinksParser mMockGenericLinksParser;
@Mock
@@ -219,9 +220,9 @@
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
- doReturn(mMockHandleMenu).when(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
- any(), any(), any(), any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
- anyInt());
+ when(mMockHandleMenuFactory.create(any(), anyInt(), any(), any(),
+ any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(mMockHandleMenu);
}
@After
@@ -742,9 +743,21 @@
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
+
// Simulate menu opening and clicking open in browser button
decor.createHandleMenu(mMockSplitScreenController);
- decor.onOpenInBrowserClick();
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any()
+ );
+ openInBrowserCaptor.getValue().invoke(TEST_URI1);
// Verify handle menu's browser link not set to captured link since link not valid after
// open in browser clicked
@@ -758,10 +771,22 @@
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Function1<Uri, Unit>> openInBrowserCaptor =
+ ArgumentCaptor.forClass(Function1.class);
decor.createHandleMenu(mMockSplitScreenController);
- decor.onOpenInBrowserClick();
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ openInBrowserCaptor.capture(),
+ any(),
+ any()
+ );
- verify(mMockOpenInBrowserClickListener).onClick(any(), any());
+ openInBrowserCaptor.getValue().invoke(TEST_URI1);
+
+ verify(mMockOpenInBrowserClickListener).accept(TEST_URI1);
}
@Test
@@ -776,13 +801,37 @@
verifyHandleMenuCreated(TEST_URI2);
}
+ @Test
+ public void handleMenu_onCloseMenuClick_closesMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ true /* relayout */);
+ final ArgumentCaptor<Function0<Unit>> closeClickListener =
+ ArgumentCaptor.forClass(Function0.class);
+ decoration.createHandleMenu(mMockSplitScreenController);
+ verify(mMockHandleMenu).show(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ closeClickListener.capture(),
+ any()
+ );
+
+ closeClickListener.getValue().invoke();
+
+ verify(mMockHandleMenu).close();
+ assertFalse(decoration.isHandleMenuActive());
+ }
+
private void verifyHandleMenuCreated(@Nullable Uri uri) {
- verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), any(), any(), any(),
+ verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
}
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
- final OnTaskActionClickListener l = (taskId, tag) -> {};
+ final Function0<Unit> l = () -> Unit.INSTANCE;
decoration.setOnMaximizeOrRestoreClickListener(l);
decoration.setOnLeftSnapClickListener(l);
decoration.setOnRightSnapClickListener(l);
@@ -814,20 +863,31 @@
taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
final String genericLinkString = genericLink == null ? null : genericLink.toString();
doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
// Relayout to set captured link
- decor.relayout(taskInfo);
- return decor;
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(), true /* relayout */);
}
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
- return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory());
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(),
+ false /* relayout */);
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, boolean relayout) {
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory(), relayout);
}
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
MaximizeMenuFactory maximizeMenuFactory) {
+ return createWindowDecoration(taskInfo, maximizeMenuFactory, false /* relayout */);
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ MaximizeMenuFactory maximizeMenuFactory,
+ boolean relayout) {
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mContext, mMockDisplayController, mMockSplitScreenController,
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor,
@@ -841,6 +901,9 @@
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
windowDecor.mDecorWindowContext = mContext;
+ if (relayout) {
+ windowDecor.relayout(taskInfo);
+ }
return windowDecor;
}
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/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 6667504..2ce59ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -35,7 +35,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index cca83dd..a1c7947 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -57,6 +57,7 @@
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
/**
@@ -109,6 +110,9 @@
whenever(mockDesktopWindowDecoration.addWindow(
anyInt(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
).thenReturn(mockAdditionalViewHostViewContainer)
+ whenever(mockDesktopWindowDecoration.addWindow(
+ any<View>(), any(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockAdditionalViewHostViewContainer)
whenever(mockAdditionalViewHostViewContainer.view).thenReturn(menuView)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
@@ -226,13 +230,13 @@
}
else -> error("Invalid windowing mode")
}
- val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
- onClickListener, onTouchListener, appIcon, appName, displayController,
+ val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, appIcon, appName,
splitScreenController, shouldShowWindowingPill = true,
shouldShowNewWindowButton = true,
- openInBrowserLink = null, captionWidth = HANDLE_WIDTH, captionHeight = 50,
- captionX = captionX)
- handleMenu.show()
+ null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ captionX = captionX
+ )
+ handleMenu.show(mock(), mock(), mock(), mock(), mock(), mock(), mock())
return handleMenu
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 943c313..08a6e1b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -200,7 +200,7 @@
verify(mockTransaction).setPosition(any(), eq(rectAfterMove.left.toFloat()),
eq(rectAfterMove.top.toFloat()))
- taskPositioner.onDragPositioningEnd(
+ val endBounds = taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat() + 70,
STARTING_BOUNDS.top.toFloat() + 20
)
@@ -212,12 +212,7 @@
verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds == rectAfterEnd }},
- eq(taskPositioner))
+ Assert.assertEquals(rectAfterEnd, endBounds)
}
@Test
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 e6e2d09..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.
@@ -867,6 +871,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(
new TestRunningTaskInfoBuilder().build());
mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ mRelayoutResult.mRootView = mMockView;
windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult);
@@ -878,6 +883,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(
new TestRunningTaskInfoBuilder().build());
mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ mRelayoutResult.mRootView = mMockView;
assertThrows(IllegalArgumentException.class,
() -> windowDecor.updateViewHost(
@@ -889,10 +895,83 @@
final TestWindowDecoration windowDecor = createWindowDecoration(
new TestRunningTaskInfoBuilder().build());
mRelayoutParams.mApplyStartTransactionOnDraw = false;
+ mRelayoutResult.mRootView = mMockView;
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,
@@ -958,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/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 97971e1..3196ba1 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -23,4 +23,12 @@
namespace: "media_tv"
description: "TIAF V3.0 APIs for Android V"
bug: "303323657"
+}
+
+flag {
+ name: "tis_always_bound_permission"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
+ bug: "332201346"
}
\ No newline at end of file
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/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 8f16f76..0fb3049 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -127,3 +127,30 @@
"-DPNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR",
],
}
+
+cc_fuzz {
+ name: "imagedecoder_heif_fuzzer",
+ defaults: ["imagedecoder_fuzzer_defaults"],
+ team: "trendy_team_android_core_graphics_stack",
+ shared_libs: [
+ "libfakeservicemanager",
+ ],
+ target: {
+ android: {
+ shared_libs: [
+ "libmediaplayerservice",
+ "libmediaextractorservice",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libbinder_random_parcel",
+ "libcutils",
+ ],
+ },
+ },
+ include_dirs: ["frameworks/av/services/mediaextractor"],
+ cflags: [
+ "-DFUZZ_HEIF_FORMAT",
+ ],
+}
diff --git a/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
index 6743997..f739e4a 100644
--- a/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
+++ b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
@@ -18,6 +18,16 @@
#include <binder/IPCThreadState.h>
#include <fuzzer/FuzzedDataProvider.h>
+#ifdef FUZZ_HEIF_FORMAT
+#include <fakeservicemanager/FakeServiceManager.h>
+#ifdef __ANDROID__
+#include <MediaExtractorService.h>
+#include <MediaPlayerService.h>
+#else
+#include <fuzzbinder/random_binder.h>
+#endif //__ANDROID__
+#endif // FUZZ_HEIF_FORMAT
+
#ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
#include <fuzz/png_mutator.h>
#endif
@@ -31,8 +41,42 @@
using PixelPointer = std::unique_ptr<void, PixelFreer>;
+#ifndef FUZZ_HEIF_FORMAT
+#define FOURCC(c1, c2, c3, c4) ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4))
+/** Reverse all 4 bytes in a 32bit value.
+ e.g. 0x12345678 -> 0x78563412
+*/
+static uint32_t endianSwap32(uint32_t value) {
+ return ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) |
+ (value >> 24);
+}
+
+static bool isFtyp(const uint8_t* data, size_t size) {
+ constexpr int32_t headerSize = 8;
+ constexpr int32_t chunkTypeOffset = 4;
+ constexpr int32_t ftypFourCCVal = FOURCC('f', 't', 'y', 'p');
+ if (size >= headerSize) {
+ const uint32_t* chunk = reinterpret_cast<const uint32_t*>(data + chunkTypeOffset);
+ if (endianSwap32(*chunk) == ftypFourCCVal) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
AImageDecoder* init(const uint8_t* data, size_t size, bool useFileDescriptor) {
AImageDecoder* decoder = nullptr;
+#ifndef FUZZ_HEIF_FORMAT
+ if (isFtyp(data, size)) {
+ /* We want to ignore HEIF data when fuzzing non-HEIF image decoders. Use 'FTYP'
+ * as a signal to ignore, though note that this excludes more than just HEIF.
+ * But when this code was added, `AImageDecoder` did not support any formats
+ * in 'FTYP' besides HEIF.
+ */
+ return nullptr;
+ }
+#endif // FUZZ_HEIF_FORMAT
if (useFileDescriptor) {
constexpr char testFd[] = "tempFd";
int32_t fileDesc = open(testFd, O_RDWR | O_CREAT | O_TRUNC);
@@ -47,6 +91,27 @@
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider dataProvider = FuzzedDataProvider(data, size);
+#ifdef FUZZ_HEIF_FORMAT
+ /**
+ * For image formats like HEIF, a new metadata object is
+ * created which requires "media.player" service running
+ */
+ static std::once_flag callOnceHEIF;
+ std::call_once(callOnceHEIF, [&]() {
+ android::sp<android::IServiceManager> fakeServiceManager =
+ new android::FakeServiceManager();
+ setDefaultServiceManager(fakeServiceManager);
+#ifdef __ANDROID__
+ android::MediaPlayerService::instantiate();
+ android::MediaExtractorService::instantiate();
+#else
+ auto binderExtractor = android::getRandomBinder(&dataProvider);
+ auto binderPlayer = android::getRandomBinder(&dataProvider);
+ fakeServiceManager->addService(android::String16("media.extractor"), binderExtractor);
+ fakeServiceManager->addService(android::String16("media.player"), binderPlayer);
+#endif //__ANDROID__
+ });
+#endif // FUZZ_HEIF_FORMAT
/**
* Use maximum of 80% of buffer for creating decoder and save at least
* 20% buffer for fuzzing other APIs
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/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 42d0cc4..a57d6eb 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -34,7 +34,7 @@
<string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to set up</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile [CHAR LIMIT=NONE] -->
- <string name="summary_watch">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
+ <string name="summary_watch">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -45,27 +45,18 @@
<string name="profile_name_glasses">device</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
+ <string name="summary_glasses">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to access this information from your phone</string>
-
- <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
- <string name="title_app_streaming_with_mirroring">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your phone\u2019s apps?</string>
+ <string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
<!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_app_streaming">%1$s will have access to anything that’s visible or played on the phone, including audio, photos, passwords, and messages.<br/><br/>%1$s will be able to stream apps until you remove access to this permission.</string>
-
- <!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
- <string name="helper_title_app_streaming">Cross-device services</string>
+ <string name="summary_app_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on the <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
-
- <!-- Description of the helper dialog for APP_STREAMING profile with mirroring enabled. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming_with_mirroring"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+ <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
@@ -78,30 +69,24 @@
<!-- ================= DEVICE_PROFILE_COMPUTER ================= -->
<!-- Confirmation for associating an application with a companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_computer">Allow <strong><xliff:g id="app_name" example="GMS">%1$s</xliff:g></strong> to access this information from your phone</string>
+ <string name="title_computer">Allow <strong><xliff:g id="app_name" example="GMS">%1$s</xliff:g></strong> to access this information from your <xliff:g id="device_type" example="phone">%2$s</xliff:g></string>
<!-- Description of the privileges the application will get if associated with the companion device of COMPUTER profile (type) [CHAR LIMIT=NONE] -->
<string name="summary_computer"></string>
- <!-- Title of the helper dialog for COMPUTER profile [CHAR LIMIT=30]. -->
- <string name="helper_title_computer">Google Play services</string>
-
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
+ <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to access your <xliff:g id="device_type" example="phone">%3$s</xliff:g>\u2019s photos, media, and notifications</string>
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to take this action?</string>
-
- <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming_with_mirroring">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your phone\u2019s apps and system features?</string>
+ <string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your <xliff:g id="device_type" example="phone">%2$s</xliff:g>\u2019s apps and system features to <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string>
<!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_nearby_device_streaming">%1$s will have access to anything that’s visible or played on your phone, including audio, photos, payment info, passwords, and messages.<br/><br/>%1$s will be able to stream apps and system features until you remove access to this permission.</string>
+ <string name="summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s visible or played on your <xliff:g id="device_type" example="phone">%2$s</xliff:g>, including audio, photos, payment info, passwords, and messages.<br/><br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream apps and system features to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features between your devices</string>
<!-- ================= null profile ================= -->
@@ -173,12 +158,6 @@
<!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] -->
<string name="permission_notifications">Notifications</string>
- <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] -->
- <string name="permission_app_streaming">Apps</string>
-
- <!-- Nearby_device_streaming permission will be granted to the corresponding profile [CHAR LIMIT=45] -->
- <string name="permission_nearby_device_streaming">Streaming</string>
-
<!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_phone_summary">Make and manage phone calls</string>
@@ -206,15 +185,9 @@
<!-- Description of NLA & POST_NOTIFICATION of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_notifications_summary">\u2022 Read all notifications, including info like contacts, messages, and photos<br/>\u2022 Send notifications<br/><br/>You can manage this app\'s ability to read and send notifications anytime in Settings > Notifications.</string>
- <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
- <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string>
-
<!-- Description of storage permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_storage_summary"></string>
- <!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
- <string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
-
<!-- Description of change media output permission to be granted to the corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_media_routing_control_summary">Access a list of available devices and control which one streams or casts audio or video from other apps</string>
@@ -224,4 +197,10 @@
<!-- The type of the device for tablet [CHAR LIMIT=30] -->
<string name="device_type" product="tablet">tablet</string>
+ <!-- The type of the device for TV [CHAR LIMIT=30] -->
+ <string name="device_type" product="tv">tv</string>
+
+ <!-- The type of the device for other device [CHAR LIMIT=30] -->
+ <string name="device_type" product="device">device</string>
+
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index e005334..6117330 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -440,12 +440,13 @@
return;
}
- title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
+ title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), mAppLabel,
+ getString(R.string.device_type), deviceName);
if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
final Spanned summary = getHtmlFromResources(this, summaryResourceId,
- deviceName);
+ mAppLabel, getString(R.string.device_type), deviceName);
mSummary.setText(summary);
} else {
mSummary.setVisibility(View.GONE);
@@ -646,6 +647,11 @@
// and when mPermissionListRecyclerView is fully populated.
// Lastly, disable the Allow and Don't allow buttons.
private void setupPermissionList(String deviceProfile) {
+ if (!PROFILE_PERMISSIONS.containsKey(deviceProfile)) {
+ // Nothing to do if there are no permission types.
+ return;
+ }
+
final List<Integer> permissionTypes = new ArrayList<>(
PROFILE_PERMISSIONS.get(deviceProfile));
if (permissionTypes.isEmpty()) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index dc68bcc..37b1f29 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -27,13 +27,11 @@
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
-import android.companion.virtual.flags.Flags;
import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -47,31 +45,27 @@
// Permission resources
private static final int PERMISSION_NOTIFICATION_LISTENER_ACCESS = 0;
private static final int PERMISSION_STORAGE = 1;
- private static final int PERMISSION_APP_STREAMING = 2;
- private static final int PERMISSION_PHONE = 3;
- private static final int PERMISSION_SMS = 4;
- private static final int PERMISSION_CONTACTS = 5;
- private static final int PERMISSION_CALENDAR = 6;
- private static final int PERMISSION_NEARBY_DEVICES = 7;
- private static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
- private static final int PERMISSION_MICROPHONE = 9;
- private static final int PERMISSION_CALL_LOGS = 10;
+ private static final int PERMISSION_PHONE = 2;
+ private static final int PERMISSION_SMS = 3;
+ private static final int PERMISSION_CONTACTS = 4;
+ private static final int PERMISSION_CALENDAR = 5;
+ private static final int PERMISSION_NEARBY_DEVICES = 6;
+ private static final int PERMISSION_MICROPHONE = 7;
+ private static final int PERMISSION_CALL_LOGS = 8;
// Notification Listener Access & POST_NOTIFICATION permission
- private static final int PERMISSION_NOTIFICATIONS = 11;
- private static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 12;
+ private static final int PERMISSION_NOTIFICATIONS = 9;
+ private static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 10;
static final Map<Integer, Integer> PERMISSION_TITLES;
static {
final Map<Integer, Integer> map = new ArrayMap<>();
map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.string.permission_notifications);
map.put(PERMISSION_STORAGE, R.string.permission_storage);
- map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
map.put(PERMISSION_PHONE, R.string.permission_phone);
map.put(PERMISSION_SMS, R.string.permission_sms);
map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
- map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications);
@@ -85,14 +79,11 @@
map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
R.string.permission_notification_listener_access_summary);
map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
- map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
map.put(PERMISSION_SMS, R.string.permission_sms_summary);
map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
- map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
- R.string.permission_nearby_device_streaming_summary);
map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications_summary);
@@ -105,14 +96,11 @@
final Map<Integer, Integer> map = new ArrayMap<>();
map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.drawable.ic_permission_notifications);
map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
- map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
- map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
- R.drawable.ic_permission_nearby_device_streaming);
map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
map.put(PERMISSION_NOTIFICATIONS, R.drawable.ic_permission_notifications);
@@ -124,19 +112,10 @@
static final Map<String, Integer> PROFILE_TITLES;
static {
final Map<String, Integer> map = new ArrayMap<>();
- if (Flags.interactiveScreenMirror()) {
- map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming_with_mirroring);
- } else {
- map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
- }
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection);
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
- if (Flags.interactiveScreenMirror()) {
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
- R.string.title_nearby_device_streaming_with_mirroring);
- } else {
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
- }
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
@@ -149,29 +128,29 @@
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
- if (Flags.interactiveScreenMirror()) {
- map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
- R.string.summary_nearby_device_streaming);
- }
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.summary_nearby_device_streaming);
map.put(null, R.string.summary_generic);
PROFILE_SUMMARIES = unmodifiableMap(map);
}
+ static final Map<String, Integer> PROFILE_HELPER_SUMMARIES;
+ static {
+ final Map<String, Integer> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.helper_summary_app_streaming);
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ R.string.helper_summary_nearby_device_streaming);
+ map.put(DEVICE_PROFILE_COMPUTER, R.string.helper_summary_computer);
+
+ PROFILE_HELPER_SUMMARIES = unmodifiableMap(map);
+ }
+
static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
static {
final Map<String, List<Integer>> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
- if (Flags.interactiveScreenMirror()) {
- map.put(DEVICE_PROFILE_APP_STREAMING, Collections.emptyList());
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Collections.emptyList());
- } else {
- map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
- Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
- }
if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index fe0e021..ec92987 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -16,17 +16,13 @@
package com.android.companiondevicemanager;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
-
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_HELPER_SUMMARIES;
import static com.android.companiondevicemanager.Utils.getApplicationIcon;
import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import android.annotation.Nullable;
import android.companion.AssociationRequest;
-import android.companion.virtual.flags.Flags;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -105,7 +101,7 @@
final String deviceProfile = request.getDeviceProfile();
final String packageName = request.getPackageName();
- final CharSequence displayName = request.getDisplayName();
+ final CharSequence deviceName = request.getDisplayName();
final int userId = request.getUserId();
final CharSequence appLabel;
@@ -123,37 +119,17 @@
mAppIcon = view.findViewById(R.id.app_icon);
mButton = view.findViewById(R.id.btn_back);
- final CharSequence title;
- final Spanned summary;
-
- switch (deviceProfile) {
- case DEVICE_PROFILE_APP_STREAMING:
- title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
- summary = getHtmlFromResources(
- getContext(), Flags.interactiveScreenMirror()
- ? R.string.helper_summary_app_streaming_with_mirroring
- : R.string.helper_summary_app_streaming, title, displayName);
- break;
-
- case DEVICE_PROFILE_COMPUTER:
- title = getHtmlFromResources(getContext(), R.string.helper_title_computer);
- summary = getHtmlFromResources(
- getContext(), R.string.helper_summary_computer, title, displayName);
- break;
-
- case DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:
- title = appLabel;
- summary = getHtmlFromResources(
- getContext(), R.string.helper_summary_nearby_device_streaming, title,
- displayName);
- break;
-
- default:
- throw new RuntimeException("Unsupported profile " + deviceProfile);
+ if (PROFILE_HELPER_SUMMARIES.containsKey(deviceProfile)) {
+ final int summaryResourceId = PROFILE_HELPER_SUMMARIES.get(deviceProfile);
+ final Spanned summary = getHtmlFromResources(
+ getContext(), summaryResourceId, appLabel, deviceName,
+ getString(R.string.device_type));
+ mSummary.setText(summary);
+ } else {
+ throw new RuntimeException("Unsupported profile " + deviceProfile);
}
- mTitle.setText(title);
- mSummary.setText(summary);
+ mTitle.setText(appLabel);
mAppIcon.setImageDrawable(applicationIcon);
mButton.setOnClickListener(v -> {
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
index c6013e2..ef4416e 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -30,7 +30,7 @@
import com.google.common.truth.Truth.assertThat
import com.android.credentialmanager.ui.mappers.toGet
import com.android.credentialmanager.model.get.ProviderInfo
-import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerNameEntries
/** Unit tests for [CredentialSelectorUiStateGetMapper]. */
@SmallTest
@@ -108,7 +108,7 @@
}
@Test
- fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() {
+ fun `On primary screen, multiple accounts returns MultipleEntryPrimaryScreen`() {
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
@@ -135,7 +135,7 @@
assertThat(getCredentialUiState).isEqualTo(
CredentialSelectorUiState.Get.MultipleEntry(
- listOf(PerUserNameEntries("userName", listOf(
+ listOf(PerNameEntries("userName", listOf(
passkeyCredentialEntryInfo,
passwordCredentialEntryInfo))
),
@@ -155,7 +155,7 @@
assertThat(getCredentialUiState).isEqualTo(
CredentialSelectorUiState.Get.MultipleEntry(
listOf(
- PerUserNameEntries("userName",
+ PerNameEntries("userName",
listOf(
recentlyUsedPasskeyCredential, // from provider 2
passkeyCredentialEntryInfo, // from provider 1 or 2
@@ -164,7 +164,7 @@
passwordCredentialEntryInfo, // from provider 1 or 2
passwordCredentialEntryInfo, // from provider 1 or 2
)),
- PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
+ PerNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
),
listOf(actionEntryInfo, actionEntryInfo),
listOf(authenticationEntryInfo, authenticationEntryInfo)
@@ -172,8 +172,44 @@
)
}
+ @Test
+ fun `Returned multiple entry is grouped by display name if present`() {
+ val testCred1 = createCredentialEntryInfo(displayName = "testDisplayName",
+ userName = "testUserName", credentialType = CredentialType.PASSWORD)
+ val testCred2 = createCredentialEntryInfo(displayName = "testDisplayName",
+ userName = "testUserName", credentialType = CredentialType.PASSKEY)
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1),
+ createProviderInfo(credentialList2),
+ createProviderInfo(listOf(testCred1, testCred2))))
+ .toGet(isPrimary = false)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.MultipleEntry(
+ listOf(
+ PerNameEntries("userName",
+ listOf(
+ recentlyUsedPasskeyCredential, // from provider 2
+ passkeyCredentialEntryInfo, // from provider 1 or 2
+ passkeyCredentialEntryInfo, // from provider 1 or 2
+ recentlyUsedPasswordCredential, // from provider 2
+ passwordCredentialEntryInfo, // from provider 1 or 2
+ passwordCredentialEntryInfo, // from provider 1 or 2
+ )),
+ PerNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
+ PerNameEntries("testDisplayName", listOf(testCred2, testCred1)),
+ ),
+ listOf(actionEntryInfo, actionEntryInfo, actionEntryInfo),
+ listOf(authenticationEntryInfo, authenticationEntryInfo, authenticationEntryInfo)
+ )
+ )
+ }
+
fun createCredentialEntryInfo(
userName: String,
+ displayName: String? = null,
credentialType: CredentialType = CredentialType.PASSKEY,
lastUsedTimeMillis: Long = 0L
): CredentialEntryInfo =
@@ -188,7 +224,7 @@
credentialTypeDisplayName = "",
providerDisplayName = "",
userName = userName,
- displayName = "",
+ displayName = displayName,
icon = mDrawable,
shouldTintIcon = false,
lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis),
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
index b2f55c1..2f5ec72 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -81,12 +81,12 @@
) : Get()
/** Getting credential UI state on secondary screen when there are multiple accounts available. */
data class MultipleEntry(
- val accounts: List<PerUserNameEntries>,
+ val accounts: List<PerNameEntries>,
val actionEntryList: List<ActionEntryInfo>,
val authenticationEntryList: List<AuthenticationEntryInfo>,
) : Get() {
- data class PerUserNameEntries(
- val userName: String,
+ data class PerNameEntries(
+ val name: String,
val sortedCredentialEntryList: List<CredentialEntryInfo>,
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 2656275..7dfe742e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -19,15 +19,18 @@
import android.graphics.drawable.Drawable
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.CredentialSelectorUiState
-import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerNameEntries
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.CredentialEntryInfo
import java.time.Instant
fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
+
val accounts = providerInfos
.flatMap { it.credentialEntryList }
- .groupBy { it.userName}
+ .groupBy {
+ if (it.displayName.isNullOrBlank()) it.userName else checkNotNull(it.displayName)
+ }
.entries
.toList()
@@ -56,7 +59,7 @@
}
} else {
CredentialSelectorUiState.Get.MultipleEntry(
- accounts = accounts.map { PerUserNameEntries(
+ accounts = accounts.map { PerNameEntries(
it.key,
it.value.sortedWith(comparator)
)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 2af5be8..ef32c94 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -79,7 +79,7 @@
Row {
Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
WearSecondaryLabel(
- text = userNameEntries.userName,
+ text = userNameEntries.name,
modifier = Modifier.padding(
top = 12.dp,
bottom = 4.dp,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index fd0fc8c..14cc9bf 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -55,15 +55,15 @@
},
accountContent = {
val displayName = entry.displayName
- if (displayName == null ||
+ if (displayName.isNullOrBlank() ||
entry.displayName.equals(entry.userName, ignoreCase = true)) {
AccountRow(
primaryText = entry.userName,
)
} else {
AccountRow(
- primaryText = displayName,
- secondaryText = entry.userName,
+ primaryText = entry.userName,
+ secondaryText = displayName,
)
}
},
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 83a986b1..0d124e8 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -9,16 +9,6 @@
}
flag {
- name: "enable_cached_bluetooth_device_dedup"
- namespace: "bluetooth"
- description: "Enable dedup in CachedBluetoothDevice"
- bug: "319197962"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "bluetooth_qs_tile_dialog_auto_on_toggle"
namespace: "bluetooth"
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
@@ -118,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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5c4cdb2..687c728 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -881,6 +881,11 @@
<!-- UI debug setting: show physical key presses summary [CHAR LIMIT=150] -->
<string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+ <!-- UI debug setting: Title text for a debug setting that enables a visualization of touchpad input in a window on the screen [CHAR LIMIT=50] -->
+ <string name="touchpad_visualizer">Show touchpad input</string>
+ <!-- UI debug setting: Summary text for a debug setting that enables a visualization of touchpad input in a window on the screen [CHAR LIMIT=150] -->
+ <string name="touchpad_visualizer_summary">Screen overlay displaying touchpad input data and recognized gestures</string>
+
<!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
<string name="show_screen_updates">Show surface updates</string>
<!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index e926b16..0dc772a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -16,8 +16,6 @@
package com.android.settingslib.bluetooth;
-import static com.android.settingslib.flags.Flags.enableCachedBluetoothDeviceDedup;
-
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -403,7 +401,7 @@
cachedDevice = mDeviceManager.addDevice(device);
}
- if (enableCachedBluetoothDeviceDedup() && bondState == BluetoothDevice.BOND_BONDED) {
+ if (bondState == BluetoothDevice.BOND_BONDED) {
mDeviceManager.removeDuplicateInstanceForIdentityAddress(device);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 2573907..de60fdc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -578,7 +578,7 @@
}
/**
- * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+ * Check if {@link CachedBluetoothDevice} (lead or member) has connected to a broadcast source.
*
* @param cachedDevice The cached bluetooth device to check.
* @param localBtManager The BT manager to provide BT functions.
@@ -586,20 +586,10 @@
*/
@WorkerThread
public static boolean hasConnectedBroadcastSource(
- CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
- if (localBtManager == null) {
- Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
- return false;
- }
- LocalBluetoothLeBroadcastAssistant assistant =
- localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
- if (assistant == null) {
- Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
- return false;
- }
- List<BluetoothLeBroadcastReceiveState> sourceList =
- assistant.getAllSources(cachedDevice.getDevice());
- if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) {
+ @Nullable CachedBluetoothDevice cachedDevice,
+ @Nullable LocalBluetoothManager localBtManager) {
+ if (cachedDevice == null) return false;
+ if (hasConnectedBroadcastSourceForBtDevice(cachedDevice.getDevice(), localBtManager)) {
Log.d(
TAG,
"Lead device has connected broadcast source, device = "
@@ -608,9 +598,7 @@
}
// Return true if member device is in broadcast.
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
- List<BluetoothLeBroadcastReceiveState> list =
- assistant.getAllSources(device.getDevice());
- if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) {
+ if (hasConnectedBroadcastSourceForBtDevice(device.getDevice(), localBtManager)) {
Log.d(
TAG,
"Member device has connected broadcast source, device = "
@@ -621,6 +609,28 @@
return false;
}
+ /**
+ * Check if {@link BluetoothDevice} has connected to a broadcast source.
+ *
+ * @param device The bluetooth device to check.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return Whether the device has connected to a broadcast source.
+ */
+ @WorkerThread
+ public static boolean hasConnectedBroadcastSourceForBtDevice(
+ @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager == null
+ ? null
+ : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (device == null || assistant == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(device);
+ return !sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected);
+ }
+
/** Checks the connectivity status based on the provided broadcast receive state. */
@WorkerThread
public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
@@ -805,8 +815,10 @@
ComponentName exclusiveManagerComponent =
ComponentName.unflattenFromString(exclusiveManagerName);
- String exclusiveManagerPackage = exclusiveManagerComponent != null
- ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName;
+ String exclusiveManagerPackage =
+ exclusiveManagerComponent != null
+ ? exclusiveManagerComponent.getPackageName()
+ : exclusiveManagerName;
if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) {
return false;
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/LocalBluetoothProfileManagerCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerCallbackExt.kt
new file mode 100644
index 0000000..b7338fc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerCallbackExt.kt
@@ -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.settingslib.bluetooth
+
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [LocalBluetoothProfileManager.ServiceListener] service state changes */
+val LocalBluetoothProfileManager.onServiceStateChanged: Flow<Unit>
+ get() =
+ callbackFlow {
+ val listener =
+ object : LocalBluetoothProfileManager.ServiceListener {
+ override fun onServiceConnected() {
+ launch { trySend(Unit) }
+ }
+
+ override fun onServiceDisconnected() {
+ launch { trySend(Unit) }
+ }
+ }
+ addServiceListener(listener)
+ awaitClose { removeServiceListener(listener) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
\ No newline at end of file
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/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
index d69c87b..4b6bcf9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.Log
import android.view.WindowManager
import androidx.lifecycle.LifecycleOwner
@@ -31,12 +32,19 @@
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeoutException
import kotlin.coroutines.resume
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOn
/** A util for Satellite dialog */
object SatelliteDialogUtils {
@@ -70,7 +78,7 @@
coroutineScope.launch {
var isSatelliteModeOn = false
try {
- isSatelliteModeOn = requestIsEnabled(context)
+ isSatelliteModeOn = requestIsSessionStarted(context)
} catch (e: InterruptedException) {
Log.w(TAG, "Error to get satellite status : $e")
} catch (e: ExecutionException) {
@@ -118,19 +126,107 @@
}
suspendCancellableCoroutine {continuation ->
- satelliteManager?.requestIsEnabled(Default.asExecutor(),
- object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
- override fun onResult(result: Boolean) {
- Log.i(TAG, "Satellite modem enabled status: $result")
- continuation.resume(result)
- }
+ try {
+ satelliteManager?.requestIsEnabled(Default.asExecutor(),
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ override fun onResult(result: Boolean) {
+ Log.i(TAG, "Satellite modem enabled status: $result")
+ continuation.resume(result)
+ }
- override fun onError(error: SatelliteManager.SatelliteException) {
- super.onError(error)
- Log.w(TAG, "Can't get satellite modem enabled status", error)
- continuation.resume(false)
- }
- })
+ override fun onError(error: SatelliteManager.SatelliteException) {
+ super.onError(error)
+ Log.w(TAG, "Can't get satellite modem enabled status", error)
+ continuation.resume(false)
+ }
+ })
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ continuation.resume(false)
+ }
+ }
+ }
+
+ private suspend fun requestIsSessionStarted(
+ context: Context
+ ): Boolean = withContext(Default) {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return@withContext false
+ }
+
+ getIsSessionStartedFlow(context).conflate().first()
+ }
+
+ /**
+ * Provides a Flow that emits the session state of the satellite modem. Updates are triggered
+ * when the modem state changes.
+ *
+ * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
+ * @return A Flow emitting `true` when the session is started and `false` otherwise.
+ */
+ private fun getIsSessionStartedFlow(
+ context: Context
+ ): Flow<Boolean> {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return flowOf(false)
+ }
+
+ return callbackFlow {
+ val callback = SatelliteModemStateCallback { state ->
+ val isSessionStarted = isSatelliteSessionStarted(state)
+ Log.i(TAG, "Satellite modem state changed: state=$state"
+ + ", isSessionStarted=$isSessionStarted")
+ trySend(isSessionStarted)
+ }
+
+ var registerResult = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
+ try {
+ registerResult = satelliteManager.registerForModemStateChanged(
+ Default.asExecutor(),
+ callback
+ )
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ }
+
+
+ if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+ // If the registration failed (e.g., device doesn't support satellite),
+ // SatelliteManager will not emit the current state by callback.
+ // We send `false` value by ourself to make sure the flow has initial value.
+ Log.w(TAG, "Failed to register for satellite modem state change: $registerResult")
+ trySend(false)
+ }
+
+ awaitClose {
+ try {
+ satelliteManager.unregisterForModemStateChanged(callback)
+ } catch (e: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException: $e")
+ }
+ }
+ }.flowOn(Default)
+ }
+
+
+ /**
+ * Check if the modem is in a satellite session.
+ *
+ * @param state The SatelliteModemState provided by the SatelliteManager.
+ * @return `true` if the modem is in a satellite session, `false` otherwise.
+ */
+ fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean {
+ return when (state) {
+ SatelliteManager.SATELLITE_MODEM_STATE_OFF,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false
+ else -> true
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index 99d5891..7a66335 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -30,6 +30,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.onBroadcastStartedOrStopped
import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
+import com.android.settingslib.bluetooth.onServiceStateChanged
import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
@@ -90,13 +91,24 @@
private val coroutineScope: CoroutineScope,
private val backgroundCoroutineContext: CoroutineContext,
) : AudioSharingRepository {
+ private val isAudioSharingProfilesReady: StateFlow<Boolean> =
+ btManager.profileManager.onServiceStateChanged
+ .map { isAudioSharingProfilesReady() }
+ .onStart { emit(isAudioSharingProfilesReady()) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
+
override val inAudioSharing: Flow<Boolean> =
- btManager.profileManager.leAudioBroadcastProfile?.let { broadcast ->
- broadcast.onBroadcastStartedOrStopped
- .map { isBroadcasting() }
- .onStart { emit(isBroadcasting()) }
- .flowOn(backgroundCoroutineContext)
- } ?: flowOf(false)
+ isAudioSharingProfilesReady.flatMapLatest { ready ->
+ if (ready) {
+ btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped
+ .map { isBroadcasting() }
+ .onStart { emit(isBroadcasting()) }
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(false)
+ }
+ }
private val primaryChange: Flow<Unit> = callbackFlow {
val callback =
@@ -108,7 +120,8 @@
contentResolver.registerContentObserver(
Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
false,
- callback)
+ callback
+ )
awaitClose { contentResolver.unregisterContentObserver(callback) }
}
@@ -120,64 +133,80 @@
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
- BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver))
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+ )
override val secondaryGroupId: StateFlow<Int> =
merge(
- btManager.profileManager.leAudioBroadcastAssistantProfile
- ?.onSourceConnectedOrRemoved
- ?.map { getSecondaryGroupId() } ?: emptyFlow(),
- btManager.eventManager.onProfileConnectionStateChanged
- .filter { profileConnection ->
- profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
+ isAudioSharingProfilesReady.flatMapLatest { ready ->
+ if (ready) {
+ btManager.profileManager.leAudioBroadcastAssistantProfile
+ .onSourceConnectedOrRemoved
+ .map { getSecondaryGroupId() }
+ } else {
+ emptyFlow()
+ }
+ },
+ btManager.eventManager.onProfileConnectionStateChanged
+ .filter { profileConnection ->
+ profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
profileConnection.bluetoothProfile ==
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
- }
- .map { getSecondaryGroupId() },
- primaryGroupId.map { getSecondaryGroupId() })
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ }
+ .map { getSecondaryGroupId() },
+ primaryGroupId.map { getSecondaryGroupId() })
.onStart { emit(getSecondaryGroupId()) }
.flowOn(backgroundCoroutineContext)
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+ )
override val volumeMap: StateFlow<GroupIdToVolumes> =
- (btManager.profileManager.volumeControlProfile?.let { volumeControl ->
- inAudioSharing.flatMapLatest { isSharing ->
- if (isSharing) {
- callbackFlow {
- val callback =
- object : BluetoothVolumeControl.Callback {
- override fun onDeviceVolumeChanged(
- device: BluetoothDevice,
- @IntRange(
- from = AUDIO_SHARING_VOLUME_MIN.toLong(),
- to = AUDIO_SHARING_VOLUME_MAX.toLong())
- volume: Int
- ) {
- launch { send(Pair(device, volume)) }
- }
- }
- // Once registered, we will receive the initial volume of all
- // connected BT devices on VolumeControlProfile via callbacks
- volumeControl.registerCallback(
- ConcurrentUtils.DIRECT_EXECUTOR, callback)
- awaitClose { volumeControl.unregisterCallback(callback) }
+ inAudioSharing.flatMapLatest { isSharing ->
+ if (isSharing) {
+ callbackFlow {
+ val callback =
+ object : BluetoothVolumeControl.Callback {
+ override fun onDeviceVolumeChanged(
+ device: BluetoothDevice,
+ @IntRange(
+ from = AUDIO_SHARING_VOLUME_MIN.toLong(),
+ to = AUDIO_SHARING_VOLUME_MAX.toLong()
+ )
+ volume: Int
+ ) {
+ launch { send(Pair(device, volume)) }
}
- .runningFold(emptyMap<Int, Int>()) { acc, value ->
- val groupId =
- BluetoothUtils.getGroupId(
- btManager.cachedDeviceManager.findDevice(value.first))
- if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- acc + Pair(groupId, value.second)
- } else {
- acc
- }
- }
- .flowOn(backgroundCoroutineContext)
- } else {
- emptyFlow()
+ }
+ // Once registered, we will receive the initial volume of all
+ // connected BT devices on VolumeControlProfile via callbacks
+ btManager.profileManager.volumeControlProfile.registerCallback(
+ ConcurrentUtils.DIRECT_EXECUTOR, callback
+ )
+ awaitClose {
+ btManager.profileManager.volumeControlProfile.unregisterCallback(
+ callback
+ )
}
}
- } ?: emptyFlow())
+ .runningFold(emptyMap<Int, Int>()) { acc, value ->
+ val groupId =
+ BluetoothUtils.getGroupId(
+ btManager.cachedDeviceManager.findDevice(value.first)
+ )
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ acc + Pair(groupId, value.second)
+ } else {
+ acc
+ }
+ }
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ emptyFlow()
+ }
+ }
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
override suspend fun setSecondaryVolume(
@@ -196,12 +225,25 @@
}
}
+ private fun isBroadcastProfileReady(): Boolean =
+ btManager.profileManager.leAudioBroadcastProfile?.isProfileReady ?: false
+
+ private fun isAssistantProfileReady(): Boolean =
+ btManager.profileManager.leAudioBroadcastAssistantProfile?.isProfileReady ?: false
+
+ private fun isVolumeControlProfileReady(): Boolean =
+ btManager.profileManager.volumeControlProfile?.isProfileReady ?: false
+
+ private fun isAudioSharingProfilesReady(): Boolean =
+ isBroadcastProfileReady() && isAssistantProfileReady() && isVolumeControlProfileReady()
+
private fun isBroadcasting(): Boolean =
btManager.profileManager.leAudioBroadcastProfile?.isEnabled(null) ?: false
private fun getSecondaryGroupId(): Int =
BluetoothUtils.getGroupId(
- BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager))
+ BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
+ )
}
class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
@@ -215,5 +257,6 @@
override suspend fun setSecondaryVolume(
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
volume: Int
- ) {}
+ ) {
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index c54a2e4..078f0c8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -57,6 +57,7 @@
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.Spy
@@ -145,8 +146,11 @@
}
@Test
- fun audioSharingStateChange_emitValues() {
+ fun audioSharingStateChange_profileReady_emitValues() {
testScope.runTest {
+ `when`(broadcast.isProfileReady).thenReturn(true)
+ `when`(assistant.isProfileReady).thenReturn(true)
+ `when`(volumeControl.isProfileReady).thenReturn(true)
val states = mutableListOf<Boolean?>()
underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
runCurrent()
@@ -155,7 +159,19 @@
triggerAudioSharingStateChange(TriggerType.BROADCAST_START, broadcastStarted)
runCurrent()
- Truth.assertThat(states).containsExactly(true, false, true)
+ Truth.assertThat(states).containsExactly(false, true, false, true)
+ }
+ }
+
+ @Test
+ fun audioSharingStateChange_profileNotReady_broadcastCallbackNotRegistered() {
+ testScope.runTest {
+ val states = mutableListOf<Boolean?>()
+ underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+
+ Truth.assertThat(states).containsExactly(false)
}
}
@@ -176,11 +192,24 @@
}
@Test
- fun secondaryGroupIdChange_emitValues() {
+ fun secondaryGroupIdChange_profileNotReady_assistantCallbackNotRegistered() {
testScope.runTest {
val groupIds = mutableListOf<Int?>()
underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
runCurrent()
+ verify(assistant, never()).registerServiceCallBack(any(), any())
+ }
+ }
+
+ @Test
+ fun secondaryGroupIdChange_profileReady_emitValues() {
+ testScope.runTest {
+ `when`(broadcast.isProfileReady).thenReturn(true)
+ `when`(assistant.isProfileReady).thenReturn(true)
+ `when`(volumeControl.isProfileReady).thenReturn(true)
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
triggerSourceAdded()
runCurrent()
triggerContentObserverChange()
@@ -211,8 +240,11 @@
}
@Test
- fun volumeMapChange_emitValues() {
+ fun volumeMapChange_profileReady_emitValues() {
testScope.runTest {
+ `when`(broadcast.isProfileReady).thenReturn(true)
+ `when`(assistant.isProfileReady).thenReturn(true)
+ `when`(volumeControl.isProfileReady).thenReturn(true)
val volumeMaps = mutableListOf<GroupIdToVolumes?>()
underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
runCurrent()
@@ -234,6 +266,16 @@
}
@Test
+ fun volumeMapChange_profileNotReady_volumeControlCallbackNotRegistered() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ verify(volumeControl, never()).registerCallback(any(), any())
+ }
+ }
+
+ @Test
fun setSecondaryVolume_setValue() {
testScope.runTest {
Settings.Secure.putInt(
@@ -258,6 +300,7 @@
`when`(broadcast.isEnabled(null)).thenReturn(true)
broadcastCallbackCaptor.value.broadcastAction()
}
+
TriggerType.BROADCAST_STOP -> {
`when`(broadcast.isEnabled(null)).thenReturn(false)
broadcastCallbackCaptor.value.broadcastAction()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 370d568..4551f1e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -56,7 +56,10 @@
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
@RunWith(RobolectricTestRunner.class)
public class BluetoothUtilsTest {
@@ -557,8 +560,14 @@
}
@Test
- public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() {
+ public void testHasConnectedBroadcastSource_leadDeviceConnectedToBroadcastSource() {
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
@@ -566,7 +575,8 @@
List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
sourceList.add(mLeBroadcastReceiveState);
- when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(Collections.emptyList());
assertThat(
BluetoothUtils.hasConnectedBroadcastSource(
@@ -575,6 +585,79 @@
}
@Test
+ public void testHasConnectedBroadcastSource_memberDeviceConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(Collections.emptyList());
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSource_deviceNotConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSourceForBtDevice_deviceConnectedToBroadcastSource() {
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSourceForBtDevice_deviceNotConnectedToBroadcastSource() {
+ List<Long> bisSyncState = new ArrayList<>();
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
@@ -604,9 +687,19 @@
}
@Test
- public void getSecondaryDeviceForBroadcast_errorState_returnNull() {
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
- mLocalBluetoothManager))
+ public void getSecondaryDeviceForBroadcast_noBroadcast_returnNull() {
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
+ .isNull();
+ }
+
+ @Test
+ public void getSecondaryDeviceForBroadcast_nullProfile_returnNull() {
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
.isNull();
}
@@ -616,6 +709,7 @@
mContext.getContentResolver(),
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
1);
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
when(deviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
@@ -625,8 +719,9 @@
when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(state));
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mBluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
- mLocalBluetoothManager))
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
.isNull();
}
@@ -636,6 +731,7 @@
mContext.getContentResolver(),
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
1);
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
CachedBluetoothDeviceManager deviceManager = mock(CachedBluetoothDeviceManager.class);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(deviceManager);
CachedBluetoothDevice cachedBluetoothDevice = mock(CachedBluetoothDevice.class);
@@ -655,8 +751,9 @@
when(mAssistant.getAllConnectedDevices())
.thenReturn(ImmutableList.of(mBluetoothDevice, bluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
- mLocalBluetoothManager))
+ assertThat(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(
+ mContext.getContentResolver(), mLocalBluetoothManager))
.isEqualTo(mCachedBluetoothDevice);
}
}
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/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
index aeda1ed6..2078b36 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -17,11 +17,12 @@
package com.android.settingslib.satellite
import android.content.Context
-import android.content.Intent
-import android.os.OutcomeReceiver
import android.platform.test.annotations.RequiresFlagsEnabled
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.AndroidRuntimeException
import androidx.test.core.app.ApplicationProvider
import com.android.internal.telephony.flags.Flags
@@ -67,26 +68,19 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onResult(true)
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(1)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_ENABLING_SATELLITE)
null
}
try {
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertTrue(it)
- })
+ assertTrue(it)
+ })
} catch (e: AndroidRuntimeException) {
// Catch exception of starting activity .
}
@@ -95,68 +89,61 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onResult(false)
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(1)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
null
}
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
- `when`(context.getSystemService(SatelliteManager::class.java))
- .thenReturn(null)
+ `when`(context.getSystemService(SatelliteManager::class.java)).thenReturn(null)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
- .thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
- null
- }
-
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenReturn(SATELLITE_RESULT_MODEM_ERROR)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_phoneCrash_notShowWarningDialog() = runBlocking {
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenThrow(IllegalStateException("Telephony is null!!!"))
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any())
}
}
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/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 7b927d7..2823277 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -200,6 +200,7 @@
VALIDATORS.put(System.POINTER_LOCATION, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.SHOW_TOUCHES, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.SHOW_KEY_PRESSES, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_VISUALIZER, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.SHOW_ROTARY_INPUT, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index cd37ad1..3c24f5c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2831,6 +2831,9 @@
Settings.System.SHOW_KEY_PRESSES,
SystemSettingsProto.DevOptions.SHOW_KEY_PRESSES);
dumpSetting(s, p,
+ Settings.System.TOUCHPAD_VISUALIZER,
+ SystemSettingsProto.DevOptions.TOUCHPAD_VISUALIZER);
+ dumpSetting(s, p,
Settings.System.POINTER_LOCATION,
SystemSettingsProto.DevOptions.POINTER_LOCATION);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e8ef620..ba59ce8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3264,6 +3264,24 @@
if (forceNotify || success) {
notifyForSettingsChange(key, name);
+
+ // If this is an aconfig flag, it will be written as a staged flag.
+ // Notify that its staged flag value will be updated.
+ if (Flags.notifyIndividualAconfigSyspropChanged() && type == SETTINGS_TYPE_CONFIG) {
+ int slashIndex = name.indexOf('/');
+ boolean validSlashIndex = slashIndex != -1
+ && slashIndex != 0
+ && slashIndex != name.length();
+ if (validSlashIndex) {
+ String namespace = name.substring(0, slashIndex);
+ String flagName = name.substring(slashIndex + 1);
+ if (settingsState.getAconfigDefaultFlags().containsKey(flagName)) {
+ String stagedName = "staged/" + namespace + "*" + flagName;
+ notifyForSettingsChange(key, stagedName);
+ }
+ }
+ }
+
if (wasUnsetNonPredefinedSetting) {
// Increment the generation number for all non-predefined, unset settings,
// because a new non-predefined setting has been inserted
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 4f5955b..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."
@@ -52,3 +59,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "notify_individual_aconfig_sysprop_changed"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, propagate individual aconfig sys props on flag stage."
+ bug: "331963764"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 411decd..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,
@@ -918,6 +919,7 @@
Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
Settings.System.SHOW_TOUCHES,
Settings.System.SHOW_KEY_PRESSES,
+ Settings.System.TOUCHPAD_VISUALIZER,
Settings.System.SHOW_ROTARY_INPUT,
Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
Settings.System.SIP_ALWAYS, // value, not a setting
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cfd8f635..6d78705 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -576,6 +576,7 @@
"TraceurCommon",
"Traceur-res",
"//frameworks/libs/systemui:motion_tool_lib",
+ "//frameworks/libs/systemui:contextualeducationlib",
"notification_flags_lib",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
@@ -644,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",
@@ -736,6 +746,7 @@
"WindowManager-Shell",
"LowLightDreamLib",
"//frameworks/libs/systemui:motion_tool_lib",
+ "//frameworks/libs/systemui:contextualeducationlib",
"androidx.core_core-animation-testing",
"androidx.compose.ui_ui",
"flag-junit",
@@ -942,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/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index d674b6c..c881e07 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -37,6 +37,7 @@
"androidx.core_core",
"androidx.preference_preference",
"androidx.viewpager_viewpager",
+ "com_android_systemui_flags_lib",
"SettingsLibDisplayUtils",
"SettingsLibSettingsTheme",
"com_android_a11y_menu_flags_lib",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index a98625f..a7b91c2 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -18,6 +18,7 @@
package="com.android.systemui.accessibility.accessibilitymenu">
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+ <uses-permission android:name="android.permission.MANAGE_USERS"/>
<application android:supportsRtl="true">
<service
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index c1e43c9..6d79011 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -29,3 +29,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "hide_restricted_actions"
+ namespace: "accessibility"
+ description: "Hides shortcut buttons for possibly restricted actions like brightness/volume adjustment"
+ bug: "347269196"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 7b43b72..2e036e6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.accessibilitymenu.view;
+import static android.os.UserManager.DISALLOW_ADJUST_VOLUME;
+import static android.os.UserManager.DISALLOW_CONFIG_BRIGHTNESS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
@@ -24,6 +26,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
@@ -32,6 +35,8 @@
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -48,6 +53,7 @@
import androidx.annotation.NonNull;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -94,8 +100,6 @@
A11yMenuShortcut.ShortcutId.ID_SCREENSHOT_VALUE.ordinal()
};
-
-
private final AccessibilityMenuService mService;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
@@ -195,11 +199,43 @@
for (int shortcutId :
(A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)
? LARGE_SHORTCUT_LIST_DEFAULT : SHORTCUT_LIST_DEFAULT)) {
- shortcutList.add(new A11yMenuShortcut(shortcutId));
+ if (!isShortcutRestricted(shortcutId)) {
+ shortcutList.add(new A11yMenuShortcut(shortcutId));
+ }
}
return shortcutList;
}
+ @SuppressLint("MissingPermission")
+ private boolean isShortcutRestricted(int shortcutId) {
+ if (!Flags.hideRestrictedActions()) {
+ return false;
+ }
+ final UserManager userManager = mService.getSystemService(UserManager.class);
+ if (userManager == null) {
+ return false;
+ }
+ final int userId = mService.getUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
+ if (shortcutId == A11yMenuShortcut.ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()
+ || shortcutId == A11yMenuShortcut.ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
+ if (userManager.hasUserRestriction(DISALLOW_CONFIG_BRIGHTNESS)
+ || (com.android.systemui.Flags.enforceBrightnessBaseUserRestriction()
+ && userManager.hasBaseUserRestriction(
+ DISALLOW_CONFIG_BRIGHTNESS, userHandle))) {
+ return true;
+ }
+ }
+ if (shortcutId == A11yMenuShortcut.ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()
+ || shortcutId == A11yMenuShortcut.ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
+ if (userManager.hasUserRestriction(DISALLOW_ADJUST_VOLUME)
+ || userManager.hasBaseUserRestriction(DISALLOW_ADJUST_VOLUME, userHandle)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Updates a11y menu layout position by configuring layout params. */
private void updateLayoutPosition() {
final Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
@@ -326,8 +362,7 @@
return;
}
snackbar.setText(text);
- if (com.android.systemui.accessibility.accessibilitymenu
- .Flags.a11yMenuSnackbarLiveRegion()) {
+ if (Flags.a11yMenuSnackbarLiveRegion()) {
snackbar.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 395354e..9d5a2e0 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -31,6 +31,7 @@
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.junit",
+ "com_android_a11y_menu_flags_lib",
"compatibility-device-util-axt",
"platform-test-annotations",
"truth",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
index 2be9245..40f71c5 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<!-- Needed to write to Settings.Secure to enable and disable the service under test. -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+ <uses-permission android:name="android.permission.MANAGE_USERS"/>
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 991ce12..d16617f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -45,6 +45,10 @@
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
@@ -59,6 +63,7 @@
import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
import org.junit.After;
@@ -66,6 +71,7 @@
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,6 +82,9 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityMenuServiceTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = "A11yMenuServiceTest";
private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;
@@ -121,26 +130,8 @@
sDisplayManager = context.getSystemService(DisplayManager.class);
unlockSignal();
- // Disable all a11yServices if any are active.
- if (!sAccessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
- Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
- TestUtils.waitUntil("Failed to disable all services",
- TIMEOUT_SERVICE_STATUS_CHANGE_S,
- () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty());
- }
+ enableA11yMenuService(context);
- // Enable a11yMenu service.
- Settings.Secure.putString(context.getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
-
- TestUtils.waitUntil("Failed to enable service",
- TIMEOUT_SERVICE_STATUS_CHANGE_S,
- () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
- info -> info.getId().contains(SERVICE_NAME)).count() == 1);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -184,6 +175,29 @@
sUiDevice.pressHome();
}
+ private static void enableA11yMenuService(Context context) throws Throwable {
+ // Disable all a11yServices if any are active.
+ if (!sAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+ TestUtils.waitUntil("Failed to disable all services",
+ TIMEOUT_SERVICE_STATUS_CHANGE_S,
+ () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty());
+ }
+
+ // Enable a11yMenu service.
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
+
+ TestUtils.waitUntil("Failed to enable service",
+ TIMEOUT_SERVICE_STATUS_CHANGE_S,
+ () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
+ info -> info.getId().contains(SERVICE_NAME)).count() == 1);
+ }
+
private static boolean isMenuVisible() {
sUiDevice.waitForIdle();
AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
@@ -484,6 +498,54 @@
sOpenBlocked::get);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
+ public void testRestrictedActions_BrightnessNotAvailable() throws Throwable {
+ try {
+ setUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, true);
+ openMenu();
+
+ List<AccessibilityNodeInfo> buttons = getGridButtonList();
+ AccessibilityNodeInfo brightnessUpButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()));
+ AccessibilityNodeInfo brightnessDownButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()));
+
+ assertThat(brightnessUpButton).isNull();
+ assertThat(brightnessDownButton).isNull();
+ } finally {
+ setUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, false);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
+ public void testRestrictedActions_VolumeNotAvailable() throws Throwable {
+ try {
+ setUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, true);
+ openMenu();
+
+ List<AccessibilityNodeInfo> buttons = getGridButtonList();
+ AccessibilityNodeInfo volumeUpButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_VOLUME_UP_VALUE.ordinal()));
+ AccessibilityNodeInfo volumeDownButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()));
+
+ assertThat(volumeUpButton).isNull();
+ assertThat(volumeDownButton).isNull();
+ } finally {
+ setUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, false);
+ }
+ }
+
+ private void setUserRestriction(String restriction, boolean isRestricted) throws Throwable {
+ final Context context = sInstrumentation.getTargetContext();
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ userManager.setUserRestriction(restriction, isRestricted);
+ // Re-enable the service for the restriction to take effect.
+ enableA11yMenuService(context);
+ }
+
private static void unlockSignal() throws IOException {
// go/adb-cheats#unlock-screen
wakeUpScreen();
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 0cbaf29..e6fae7b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -227,17 +227,6 @@
}
flag {
- name: "notification_group_hun_removal_animation_fix"
- namespace: "systemui"
- description: "Fix the lack of hun removal animation for group notifications"
- "(not GROUP_ALERT_SUMMARY)"
- bug: "343475993"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
@@ -363,17 +352,6 @@
}
flag {
- name: "truncated_status_bar_icons_fix"
- namespace: "systemui"
- description: "Fixes the status bar icons being trunacted due to the status bar window height "
- "not being updated after certain rotations"
- bug: "323299264"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "status_bar_monochrome_icons_fix"
namespace: "systemui"
description: "Fixes the status bar icon size when drawing InsetDrawables (ie. monochrome icons)"
@@ -403,6 +381,13 @@
}
flag {
+ name: "status_bar_call_chip_notification_icon"
+ namespace: "systemui"
+ description: "Use the small icon set on the notification for the status bar call chip"
+ bug: "354930838"
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -490,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"
@@ -582,16 +555,6 @@
}
flag {
- name: "screenshot_private_profile_accessibility_announcement_fix"
- namespace: "systemui"
- description: "Modified a11y announcement for private space screenshots"
- bug: "326941376"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "screenshot_private_profile_behavior_fix"
namespace: "systemui"
description: "Private profile support for screenshots"
@@ -1073,16 +1036,6 @@
}
flag {
- namespace: "systemui"
- name: "privacy_dot_unfold_wrong_corner_fix"
- description: "Fixes an issue where the privacy dot is at the wrong corner after unfolding/folding."
- bug: "339335643"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "validate_keyboard_shortcut_helper_icon_uri"
namespace: "systemui"
description: "Adds a check that the caller can access the content URI of an icon in the shortcut helper."
@@ -1244,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."
@@ -1301,3 +1265,20 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "compose_haptic_sliders"
+ namespace: "systemui"
+ description: "Adding haptic component infrastructure to sliders in Compose."
+ bug: "341968766"
+}
+
+flag {
+ namespace: "systemui"
+ name: "settings_ext_register_content_observer_on_bg_thread"
+ description: "Register content observer in callback flow APIs on background thread in SettingsProxyExt."
+ bug: "355389014"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
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/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index d4bad23..872bef2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -27,7 +27,7 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
-import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -243,7 +243,7 @@
if (isFocusable) {
Modifier.focusable()
} else {
- Modifier.semantics { contentDescription = "" }.clearAndSetSemantics {}
+ Modifier.semantics { disabled() }.clearAndSetSemantics {}
}
)
) {
@@ -259,7 +259,7 @@
modifier =
modifier.focusable(isFocusable).semantics {
if (!isFocusable) {
- contentDescription = ""
+ disabled()
}
}
)
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/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 3cd5553..acb436e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -107,6 +107,7 @@
* The [StaticElementContentPicker] to use when deciding in which scene we should draw shared
* Elements or compose MovableElements.
*
+ * @see DefaultElementContentPicker
* @see MovableElementContentPicker
*/
override val contentPicker: StaticElementContentPicker,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 38b8383..a30b780 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -305,6 +305,11 @@
* An element picker on which we can query the set of contents (scenes or overlays) that contain the
* element. This is needed by [MovableElement], that needs to know at composition time on which of
* the candidate contents an element should be composed.
+ *
+ * @see DefaultElementContentPicker(contents)
+ * @see HighestZIndexContentPicker(contents)
+ * @see LowestZIndexContentPicker(contents)
+ * @see MovableElementContentPicker
*/
interface StaticElementContentPicker : ElementContentPicker {
/** The exhaustive lists of contents that contain this element. */
@@ -434,6 +439,11 @@
/** The default [ElementContentPicker]. */
val DefaultElementContentPicker = HighestZIndexContentPicker
+/** The [DefaultElementContentPicker] that can be used for [MovableElement]s. */
+fun DefaultElementContentPicker(contents: Set<ContentKey>): StaticElementContentPicker {
+ return HighestZIndexContentPicker(contents)
+}
+
@TransitionDsl
interface PropertyTransformationBuilder {
/**
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 7a41bc6..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
@@ -99,7 +99,9 @@
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -531,6 +533,8 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
+ advanceTimeBy(600L)
+
keyguardRepository.setDreaming(true)
keyguardRepository.setDreamingWithOverlay(true)
advanceTimeBy(60L)
@@ -639,6 +643,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
+ advanceTimeBy(600L)
keyguardRepository.setDreaming(true)
keyguardRepository.setDreamingWithOverlay(true)
advanceTimeBy(60L)
@@ -697,6 +702,7 @@
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
+ advanceTimeBy(600L)
keyguardRepository.setDreaming(true)
keyguardRepository.setDreamingWithOverlay(true)
advanceTimeBy(60L)
@@ -741,6 +747,18 @@
}
@Test
+ fun communalContent_readTriggersUmoVisibilityUpdate() =
+ testScope.runTest {
+ verify(mediaHost, never()).updateViewVisibility()
+
+ val communalContent by collectLastValue(underTest.communalContent)
+
+ // updateViewVisibility is called when the flow is collected.
+ assertThat(communalContent).isNotNull()
+ verify(mediaHost).updateViewVisibility()
+ }
+
+ @Test
fun scrollPosition_persistedOnEditEntry() {
val index = 2
val offset = 30
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/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 86c680a..023de52 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -82,7 +82,7 @@
assertFalse(launching!!)
val parent = FrameLayout(context)
- val view = CommunalAppWidgetHostView(context)
+ val view = CommunalAppWidgetHostView(context, underTest)
parent.addView(view)
val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
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 3a4b14b..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.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.android.systemui.shared.education.GestureType.BACK_GESTURE
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,8 +68,8 @@
fun changeRetrievedValueForNewUser() =
testScope.runTest {
// Update data for old user.
- underTest.incrementSignalCount(BACK_GESTURE)
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ underTest.updateGestureEduModel(BACK) { it.copy(signalCount = 1) }
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
assertThat(model?.signalCount).isEqualTo(1)
// User is changed.
@@ -81,20 +79,19 @@
}
@Test
- fun incrementSignalCount() =
+ fun dataChangedOnUpdate() =
testScope.runTest {
- underTest.incrementSignalCount(BACK_GESTURE)
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
- assertThat(model?.signalCount).isEqualTo(1)
- }
-
- @Test
- fun dataAddedOnUpdateShortcutTriggerTime() =
- testScope.runTest {
- val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
- assertThat(model?.lastShortcutTriggeredTime).isNull()
- underTest.updateShortcutTriggerTime(BACK_GESTURE)
- assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+ 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).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 01dbc6b..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.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.shared.education.GestureType
-import com.android.systemui.shared.education.GestureType.BACK_GESTURE
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_GESTURE)
+ triggerMaxEducationSignals(BACK)
val model by collectLastValue(underTest.educationTriggered)
- assertThat(model?.gestureType).isEqualTo(BACK_GESTURE)
+ 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_GESTURE)
+ 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_GESTURE)
- tryTriggeringEducation(BACK_GESTURE)
+ 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/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
index ee51e37..cd0c58f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt
@@ -20,10 +20,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.education.data.repository.contextualEducationRepository
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shared.education.GestureType.BACK_GESTURE
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -41,11 +41,9 @@
fun dataUpdatedOnIncrementSignalCount() =
testScope.runTest {
val model by
- collectLastValue(
- kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
- )
+ collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
val originalValue = model!!.signalCount
- underTest.incrementSignalCount(BACK_GESTURE)
+ underTest.incrementSignalCount(BACK)
assertThat(model?.signalCount).isEqualTo(originalValue + 1)
}
@@ -53,11 +51,9 @@
fun dataAddedOnUpdateShortcutTriggerTime() =
testScope.runTest {
val model by
- collectLastValue(
- kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK_GESTURE)
- )
+ collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK))
assertThat(model?.lastShortcutTriggeredTime).isNull()
- underTest.updateShortcutTriggerTime(BACK_GESTURE)
+ underTest.updateShortcutTriggerTime(BACK)
assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant())
}
}
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/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index a8eccc5..03647b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -136,30 +136,6 @@
}
@Test
- fun finishedKeyguardTransitionStepTests() =
- testScope.runTest {
- val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep)
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
-
- // Ignore the default state.
- assertThat(finishedSteps.subList(1, finishedSteps.size))
- .isEqualTo(listOf(steps[2], steps[5]))
- }
-
- @Test
fun startedKeyguardTransitionStepTests() =
testScope.runTest {
val startedSteps by collectValues(underTest.startedKeyguardTransitionStep)
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/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 194f362..6dbe94b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -19,11 +19,13 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalSceneRepository
@@ -68,6 +70,11 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
+@EnableFlags(
+ FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ FLAG_NEW_AOD_TRANSITION,
+ FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+)
class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -102,13 +109,6 @@
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
- if (!SceneContainerFlag.isEnabled) {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
- mSetFlagsRule.disableFlags(
- AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
- )
- }
kosmos.sceneContainerRepository.setTransitionState(transitionState)
}
@@ -212,6 +212,11 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope,
+ )
notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(false)
@@ -227,6 +232,11 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope,
+ )
notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
@@ -243,6 +253,11 @@
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope,
+ )
notificationsKeyguardInteractor.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParameters.alwaysOn).thenReturn(true)
@@ -255,6 +270,27 @@
}
@Test
+ fun iconContainer_isNotVisible_bypassDisabled_onLockscreen() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ notificationsKeyguardInteractor.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParameters.alwaysOn).thenReturn(true)
+ whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
+ notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isTrue()
+ }
+
+ @Test
fun isIconContainerVisible_stopAnimation() =
testScope.runTest {
val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
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/lifecycle/ActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
new file mode 100644
index 0000000..67517a2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.lifecycle
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActivatableTest : SysuiTestCase() {
+
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun rememberActivated() {
+ val keepAliveMutable = mutableStateOf(true)
+ var isActive = false
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ rememberActivated {
+ FakeActivatable(
+ onActivation = { isActive = true },
+ onDeactivation = { isActive = false },
+ )
+ }
+ }
+ }
+ assertThat(isActive).isTrue()
+ }
+
+ @Test
+ fun rememberActivated_leavingTheComposition() {
+ val keepAliveMutable = mutableStateOf(true)
+ var isActive = false
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ rememberActivated {
+ FakeActivatable(
+ onActivation = { isActive = true },
+ onDeactivation = { isActive = false },
+ )
+ }
+ }
+ }
+
+ // Tear down the composable.
+ composeRule.runOnUiThread { keepAliveMutable.value = false }
+ composeRule.waitForIdle()
+
+ assertThat(isActive).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt
new file mode 100644
index 0000000..9484821
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SafeActivatableTest.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.lifecycle
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SafeActivatableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val underTest = FakeActivatable()
+
+ @Test
+ fun activate() =
+ testScope.runTest {
+ assertThat(underTest.isActive).isFalse()
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.isActive).isTrue()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+ }
+
+ @Test
+ fun activate_andCancel() =
+ testScope.runTest {
+ assertThat(underTest.isActive).isFalse()
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ val job = Job()
+ underTest.activateIn(testScope, context = job)
+ runCurrent()
+ assertThat(underTest.isActive).isTrue()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.isActive).isFalse()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+ }
+
+ @Test
+ fun activate_afterCancellation() =
+ testScope.runTest {
+ assertThat(underTest.isActive).isFalse()
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ val job = Job()
+ underTest.activateIn(testScope, context = job)
+ runCurrent()
+ assertThat(underTest.isActive).isTrue()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ job.cancel()
+ runCurrent()
+ assertThat(underTest.isActive).isFalse()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.isActive).isTrue()
+ assertThat(underTest.activationCount).isEqualTo(2)
+ assertThat(underTest.cancellationCount).isEqualTo(1)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun activate_whileActive_throws() =
+ testScope.runTest {
+ assertThat(underTest.isActive).isFalse()
+ assertThat(underTest.activationCount).isEqualTo(0)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ assertThat(underTest.isActive).isTrue()
+ assertThat(underTest.activationCount).isEqualTo(1)
+ assertThat(underTest.cancellationCount).isEqualTo(0)
+
+ underTest.activateIn(testScope)
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
new file mode 100644
index 0000000..d1f908d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.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.lifecycle
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUiViewModelTest : SysuiTestCase() {
+
+ @get:Rule val composeRule = createComposeRule()
+
+ @Test
+ fun rememberActivated() {
+ val keepAliveMutable = mutableStateOf(true)
+ var isActive = false
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ rememberViewModel {
+ FakeSysUiViewModel(
+ onActivation = { isActive = true },
+ onDeactivation = { isActive = false },
+ )
+ }
+ }
+ }
+ assertThat(isActive).isTrue()
+ }
+
+ @Test
+ fun rememberActivated_withKey() {
+ val keyMutable = mutableStateOf(1)
+ var isActive1 = false
+ var isActive2 = false
+ composeRule.setContent {
+ val key by keyMutable
+ rememberViewModel(key) {
+ when (key) {
+ 1 ->
+ FakeSysUiViewModel(
+ onActivation = { isActive1 = true },
+ onDeactivation = { isActive1 = false },
+ )
+ 2 ->
+ FakeSysUiViewModel(
+ onActivation = { isActive2 = true },
+ onDeactivation = { isActive2 = false },
+ )
+ else -> error("unsupported key $key")
+ }
+ }
+ }
+ assertThat(isActive1).isTrue()
+ assertThat(isActive2).isFalse()
+
+ composeRule.runOnUiThread { keyMutable.value = 2 }
+ composeRule.waitForIdle()
+ assertThat(isActive1).isFalse()
+ assertThat(isActive2).isTrue()
+
+ composeRule.runOnUiThread { keyMutable.value = 1 }
+ composeRule.waitForIdle()
+ assertThat(isActive1).isTrue()
+ assertThat(isActive2).isFalse()
+ }
+
+ @Test
+ fun rememberActivated_leavingTheComposition() {
+ val keepAliveMutable = mutableStateOf(true)
+ var isActive = false
+ composeRule.setContent {
+ val keepAlive by keepAliveMutable
+ if (keepAlive) {
+ rememberViewModel {
+ FakeSysUiViewModel(
+ onActivation = { isActive = true },
+ onDeactivation = { isActive = false },
+ )
+ }
+ }
+ }
+
+ // Tear down the composable.
+ composeRule.runOnUiThread { keepAliveMutable.value = false }
+ composeRule.waitForIdle()
+
+ assertThat(isActive).isFalse()
+ }
+}
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/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
index aef9163..b917014 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
@@ -124,4 +124,36 @@
underTest.setIsLaunchingActivity(true)
Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(true)
}
+
+ @Test
+ fun isAnyFlingAnimationRunning() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isAnyFlingAnimationRunning)
+
+ // WHEN transitioning from QS to Gone with user input ongoing
+ val userInputOngoing = MutableStateFlow(true)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.QuickSettings,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.QuickSettings),
+ progress = MutableStateFlow(.1f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = userInputOngoing,
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN qs is not flinging
+ Truth.assertThat(actual).isFalse()
+
+ // WHEN user input ends
+ userInputOngoing.value = false
+ runCurrent()
+
+ // THEN qs is flinging
+ Truth.assertThat(actual).isTrue()
+ }
}
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 343b6bd..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.activatable.activateIn
-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.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/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
index f126432..663c341 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
@@ -18,8 +18,6 @@
import android.graphics.Point
import android.graphics.Rect
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import android.view.DisplayAdjustments
@@ -28,7 +26,6 @@
import android.widget.FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY
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.res.R
import com.android.systemui.statusbar.FakeStatusBarStateController
@@ -297,8 +294,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX)
- fun initialize_newViews_fixFlagEnabled_gravityIsUpdated() {
+ fun initialize_newViews_gravityIsUpdated() {
val newTopLeftView = initDotView()
val newTopRightView = initDotView()
val newBottomLeftView = initDotView()
@@ -318,28 +314,6 @@
.isNotEqualTo(UNSPECIFIED_GRAVITY)
}
- @Test
- @DisableFlags(Flags.FLAG_PRIVACY_DOT_UNFOLD_WRONG_CORNER_FIX)
- fun initialize_newViews_fixFlagDisabled_gravityIsNotUpdated() {
- val newTopLeftView = initDotView()
- val newTopRightView = initDotView()
- val newBottomLeftView = initDotView()
- val newBottomRightView = initDotView()
- setRotation(ROTATION_LANDSCAPE) // Bottom right used in landscape
-
- val controller = createAndInitializeController()
- // Re-init with different views, but same rotation
- controller.initialize(
- newTopLeftView,
- newTopRightView,
- newBottomLeftView,
- newBottomRightView
- )
-
- assertThat((newBottomRightView.layoutParams as FrameLayout.LayoutParams).gravity)
- .isEqualTo(UNSPECIFIED_GRAVITY)
- }
-
private fun setRotation(rotation: Int) {
whenever(mockDisplay.rotation).thenReturn(rotation)
}
@@ -347,7 +321,7 @@
private fun initDotView(): View {
val privacyDot = View(context).also { it.id = R.id.privacy_dot }
return FrameLayout(context).also {
- it.layoutParams = FrameLayout.LayoutParams(/* width = */ 0, /* height = */ 0)
+ it.layoutParams = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0)
it.addView(privacyDot)
}
}
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/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
index 5dd008a..0eea120 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -74,6 +74,26 @@
}
@Test
+ public void testRecursiveCheck() {
+ mProximityCheck.check(100, event-> mProximityCheck.check(100, mTestableCallback));
+
+ assertThat(mTestableCallback.mNumCalls).isEqualTo(0);
+ assertThat(mTestableCallback.mLastResult).isNull();
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+
+ assertThat(mTestableCallback.mNumCalls).isEqualTo(0);
+ assertThat(mTestableCallback.mLastResult).isNull();
+
+ mFakeProximitySensor.setLastEvent(new ThresholdSensorEvent(true, 0));
+ mFakeProximitySensor.alertListeners();
+
+ assertThat(mTestableCallback.mNumCalls).isEqualTo(1);
+ assertThat(mTestableCallback.mLastResult).isTrue();
+ }
+
+ @Test
public void testTimeout() {
mProximityCheck.check(100, mTestableCallback);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt
new file mode 100644
index 0000000..e281894
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/SettingsProxyExtTest.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.util.settings
+
+import android.database.ContentObserver
+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.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/** Tests for [SettingsProxyExt]. */
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class SettingsProxyExtTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ @Mock lateinit var settingsProxy: SettingsProxy
+ @Mock lateinit var userSettingsProxy: UserSettingsProxy
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagEnabled_settingsProxy_registerContentObserverInvoked() =
+ testScope.runTest {
+ val unused by collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2))
+ runCurrent()
+ verify(settingsProxy, times(2))
+ .registerContentObserver(any<String>(), any<ContentObserver>())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagDisabled_multipleSettings_SettingsProxy_registerContentObserverInvoked() =
+ testScope.runTest {
+ val unused by collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2))
+ runCurrent()
+ verify(settingsProxy, times(2))
+ .registerContentObserverSync(any<String>(), any<ContentObserver>())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagEnabled_channelClosed_settingsProxy_unregisterContentObserverInvoked() =
+ testScope.runTest {
+ val job = Job()
+ val unused by
+ collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2), context = job)
+ runCurrent()
+ job.cancel()
+ runCurrent()
+ verify(settingsProxy).unregisterContentObserverAsync(any<ContentObserver>())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagDisabled_channelClosed_settingsProxy_unregisterContentObserverInvoked() =
+ testScope.runTest {
+ val job = Job()
+ val unused by
+ collectLastValue(settingsProxy.observerFlow(SETTING_1, SETTING_2), context = job)
+ runCurrent()
+ job.cancel()
+ runCurrent()
+ verify(settingsProxy).unregisterContentObserverSync(any<ContentObserver>())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagEnabled_userSettingsProxy_registerContentObserverForUserInvoked() =
+ testScope.runTest {
+ val unused by
+ collectLastValue(userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2))
+ runCurrent()
+ verify(userSettingsProxy, times(2))
+ .registerContentObserverForUser(any<String>(), any<ContentObserver>(), any<Int>())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagDisabled_userSettingsProxy_registerContentObserverForUserInvoked() =
+ testScope.runTest {
+ val unused by
+ collectLastValue(userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2))
+ runCurrent()
+ verify(userSettingsProxy, times(2))
+ .registerContentObserverForUserSync(
+ any<String>(),
+ any<ContentObserver>(),
+ any<Int>()
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagEnabled_channelClosed_userSettingsProxy_unregisterContentObserverInvoked() =
+ testScope.runTest {
+ val job = Job()
+ val unused by
+ collectLastValue(
+ userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2),
+ context = job
+ )
+ runCurrent()
+ job.cancel()
+ runCurrent()
+ verify(userSettingsProxy).unregisterContentObserverAsync(any<ContentObserver>())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SETTINGS_EXT_REGISTER_CONTENT_OBSERVER_ON_BG_THREAD)
+ fun observeFlow_bgFlagDisabled_channelClosed_userSettingsProxy_unregisterContentObserverInvoked() =
+ testScope.runTest {
+ val job = Job()
+ val unused by
+ collectLastValue(
+ userSettingsProxy.observerFlow(userId = 0, SETTING_1, SETTING_2),
+ context = job
+ )
+ runCurrent()
+ job.cancel()
+ runCurrent()
+ verify(userSettingsProxy).unregisterContentObserverSync(any<ContentObserver>())
+ }
+
+ private companion object {
+ val SETTING_1 = "settings_1"
+ val SETTING_2 = "settings_2"
+ }
+}
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/drawable/ic_redacted_notification_single_line_icon.xml b/packages/SystemUI/res/drawable/ic_redacted_notification_single_line_icon.xml
new file mode 100644
index 0000000..8146c16
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_redacted_notification_single_line_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M12,15m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+ <path
+ android:fillColor="?android:attr/colorAccent"
+ android:pathData="M18,8h-1.5V5.5C16.5,3.01 14.49,1 12,1S7.5,3.01 7.5,5.5V8H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9.5,5.5C9.5,4.12 10.62,3 12,3c1.38,0 2.5,1.12 2.5,2.5V8h-5V5.5zM18,20H6V10h1.5h9H18V20z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
index e6ae2e1..2a00495 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
@@ -87,20 +87,21 @@
android:id="@+id/logo_description"
style="@style/TextAppearance.AuthCredential.LogoDescription"
android:layout_width="0dp"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/biometric_prompt_logo_size"
+ android:gravity="start|center_vertical"
android:textAlignment="viewStart"
- android:paddingStart="16dp"
- app:layout_constraintBottom_toBottomOf="@+id/logo"
+ android:layout_marginStart="16dp"
+ app:layout_goneMarginStart="0dp"
+ app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/logo"
- app:layout_constraintTop_toTopOf="@+id/logo" />
+ app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
style="@style/TextAppearance.AuthCredential.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -108,7 +109,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/logo"
+ app:layout_constraintTop_toBottomOf="@+id/logoBarrier"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
@@ -159,6 +160,15 @@
app:layout_constraintVertical_bias="0.0" />
<androidx.constraintlayout.widget.Barrier
+ android:id="@+id/logoBarrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:barrierMargin="12dp"
+ app:barrierAllowsGoneWidgets="false"
+ app:barrierDirection="bottom"
+ app:constraint_referenced_ids="logo_description, logo" />
+
+ <androidx.constraintlayout.widget.Barrier
android:id="@+id/contentBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml
index 84462d0..ff2fbe07 100644
--- a/packages/SystemUI/res/layout/hearing_tool_item.xml
+++ b/packages/SystemUI/res/layout/hearing_tool_item.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:gravity="center"
+ android:gravity="top|center_horizontal"
android:focusable="true"
android:clickable="true"
android:layout_weight="1">
@@ -46,8 +46,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/hearing_devices_layout_margin"
android:ellipsize="end"
- android:maxLines="1"
android:textSize="12sp"
+ android:maxLines="3"
android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
android:focusable="false" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_fingerprint_to_error_landscape.json b/packages/SystemUI/res/raw/biometricprompt_fingerprint_to_error_landscape.json
deleted file mode 100644
index 3d33b2a..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_fingerprint_to_error_landscape.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Symbol_Fingerprint_To_Error_Landscape","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,77.667,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":830,"st":-70,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json
deleted file mode 100644
index 2797996..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_folded_base_bottomright.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_portrait_base_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"biometricprompt_landscape_base","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}]}],"layers":[{"ddd":0,"ind":6,"ty":0,"nm":"biometricprompt_landscape_base","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[170,170,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":340,"h":340,"ip":0,"op":900,"st":0,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json
deleted file mode 100644
index bf65b34..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_folded_base_default.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_landscape_base","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json b/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json
deleted file mode 100644
index 7351d7c..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_folded_base_topleft.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Portrait_Base_TopLeft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
diff --git a/packages/SystemUI/res/raw/biometricprompt_landscape_base.json b/packages/SystemUI/res/raw/biometricprompt_landscape_base.json
deleted file mode 100644
index 3781eee..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_landscape_base.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_landscape_base","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_portrait_base_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_portrait_base_bottomright.json
deleted file mode 100644
index 4950666..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_portrait_base_bottomright.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_portrait_base_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"biometricprompt_landscape_base","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Null_Circle","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle mask 3","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Finger","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-60,"s":[55]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":140,"s":[10]},{"t":170,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-60,"s":[92.146,-65.896,0],"to":[1.361,6.667,0],"ti":[-1.361,-6.667,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":0,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.7,"y":0.7},"t":110,"s":[100.313,-25.896,0],"to":[0,0,0],"ti":[0,0,0]},{"t":170,"s":[100.313,-25.896,0]}],"ix":2,"l":2},"a":{"a":0,"k":[160.315,58.684,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.013,2.518],[5.251,5.023],[8.982,-2.829],[-0.264,-5.587]],"o":[[12.768,-2.854],[-14.961,2.071],[-6.004,1.89],[8.052,1.403]],"v":[[5.115,7.499],[19.814,-10.087],[-16.489,-3.588],[-24.801,8.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[34.67,28.053],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.231,-7],[-27.395,-1.197],[-26.792,4.092],[14.179,15.736]],"o":[[-17.931,5.646],[56.062,2.45],[-1.765,-22.396],[-51.819,17.744]],"v":[[-62.102,-8.314],[-39.958,30.079],[80.033,25.905],[54.879,-32.529]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[80.283,32.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"circle mask 7","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"circle mask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"circle mask 6","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"circle mask 2","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"circle mask 4","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":1,"nm":".grey900","cl":"grey900","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"circle mask 5","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":1,"nm":".black","cl":"black","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[0,18.167,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[0,10.667,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":"Shape Layer 4","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":"device frame mask","parent":24,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".blue400","cl":"blue400","parent":18,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-115.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-199,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-159,"s":[100.25,-105.667,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[100.25,-100.167,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-0.75,-14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":3,"nm":"device frame mask 5","parent":18,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-165,"op":6.00000000000001,"st":-271,"bm":0},{"ddd":0,"ind":28,"ty":4,"nm":"device frame mask 9","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":29,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-145,"s":[50]},{"t":-75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-165,"s":[0,0]},{"t":-75,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":73,"s":[50]},{"t":113,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-181,"op":-62,"st":-181,"bm":0},{"ddd":0,"ind":30,"ty":4,"nm":"device frame mask 8","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":31,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-165,"s":[50]},{"t":-95,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-195,"s":[0,0]},{"t":-105,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":43,"s":[50]},{"t":83,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-211,"op":-92,"st":-211,"bm":0},{"ddd":0,"ind":32,"ty":4,"nm":"device frame mask 7","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":33,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-195,"s":[50]},{"t":-125,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-225,"s":[0,0]},{"t":-135,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[50]},{"t":53,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-241,"op":-122,"st":-241,"bm":0},{"ddd":0,"ind":34,"ty":4,"nm":"device frame mask 6","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-29.25,-0.917,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0},{"ddd":0,"ind":35,"ty":4,"nm":".blue400","cl":"blue400","parent":23,"tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-225,"s":[50]},{"t":-155,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-255,"s":[0,0]},{"t":-165,"s":[94,94]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-17,"s":[50]},{"t":23,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-271,"op":-152,"st":-271,"bm":0}]}],"layers":[{"ddd":0,"ind":6,"ty":0,"nm":"biometricprompt_landscape_base","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[170,170,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":340,"h":340,"ip":0,"op":900,"st":0,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json b/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json
deleted file mode 100644
index 09ed225..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_portrait_base_topleft.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Portrait_Base_TopLeft","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":6,"ty":3,"nm":"Null 16","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null_Circle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[70.333,-88.75,0],"to":[-11.722,17.639,0],"ti":[11.722,-17.639,0]},{"t":-48,"s":[0,17.083,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"circle mask 3","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger_Flipped","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-24.98,-35.709,0],"ix":2,"l":2},"a":{"a":0,"k":[31.791,75.23,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[5.03,5.25],[-2.83,8.98],[-5.59,-0.26],[2.52,-11.02]],"o":[[-2.85,12.77],[2.07,-14.96],[1.9,-6],[1.4,8.05],[0,0]],"v":[[7.5,4.99],[-10.09,19.69],[-3.59,-16.61],[8.69,-24.92],[7.5,5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.760784373564,0.478431402468,0.400000029919,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.8,24.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.01,22.23],[-1.2,-27.39],[4.09,-26.79],[15.73,14.18]],"o":[[5.64,-17.93],[2.45,56.06],[-22.4,-1.77],[17.73,-51.82]],"v":[[-7.57,-66.9],[30.82,-44.76],[26.65,75.23],[-31.78,50.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431372549,0.403921598547,0.305882352941,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[31.79,75.23],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"circle mask 7","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.25,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.501960784314,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":36.9,"ix":2},"o":{"a":0,"k":114.2,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"circle mask","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-0.5,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[114.218,-17.096],[-112.938,-17.096]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"circle mask 6","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":377,"s":[-180]},{"t":417,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":377,"s":[-1.137,1.771,0],"to":[0.375,0,0],"ti":[-0.375,0,0]},{"t":417,"s":[1.113,1.771,0]}],"ix":2,"l":2},"a":{"a":0,"k":[6.238,5.063,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":77,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":137,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":167,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.333,"y":0},"t":197,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,-4.637],[-10.23,-3.195],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.7,"y":0},"t":232,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":562,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-8.788,0.393],[-10.23,1.835],[-2.196,9.843],[5.988,1.659],[4.545,0.217],[-2.196,6.948]],"c":false}]},{"t":602,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.546,-0.421],[-5.988,1.021],[-2.196,4.813],[5.988,-3.371],[4.545,-4.813],[-2.196,1.918]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.238,5.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"circle mask 2","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.308,0],"ix":2,"l":2},"a":{"a":0,"k":[41.706,20.979,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[18.645,0],[0,18.645]],"o":[[0,18.645],[-18.644,0],[0,0]],"v":[[33.76,-16.88],[-0.001,16.88],[-33.76,-16.88]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,17.13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[22.896,0],[0,22.896]],"o":[[0,22.896],[-22.896,0],[0,0]],"v":[[41.457,-20.729],[-0.001,20.729],[-41.457,-20.729]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[41.706,20.979],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"circle mask 4","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":20,"ty":1,"nm":".grey900","cl":"grey900","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,66,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[52,52,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#202124","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":"circle mask 5","parent":7,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.333,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-108,"s":[0,0]},{"t":-48,"s":[202,202]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.250980392157,0.282352941176,0.294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.078246000701,0.610494037703,0.787910970052,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-17.333],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":22,"ty":1,"nm":".black","cl":"black","parent":7,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-17.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"sw":412,"sh":300,"sc":"#000000","ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":23,"ty":4,"nm":".grey800","cl":"grey800","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-108,"s":[-192.25,99.933,0],"to":[5,3.333,0],"ti":[-5,-3.333,0]},{"t":-48,"s":[-162.25,119.933,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-163,100.85,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-108,"s":[100,100,100]},{"t":-48,"s":[59,59,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[326,201.699],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":24,"ty":4,"nm":".grey900","cl":"grey900","parent":23,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[100.25,-87.156,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[100.25,-94.656,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-171,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-141,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.512],[0,0.512],[3,3.512]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-111,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]},{"t":-81,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,3.967],[0,0.967],[3,3.967]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-199,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":25,"ty":4,"nm":"Shape Layer 4","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-116.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":-199,"s":[71,-101.083,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":365,"s":[71,-101.083,0],"to":[0,0,0],"ti":[16.833,-14.361,0]},{"t":405,"s":[-30,-14.917,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-239,"s":[29,29]},{"i":{"x":[0.833,0.833],"y":[1,0.833]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":-199,"s":[29,38]},{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":365,"s":[29,36]},{"t":405,"s":[83,83]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":365,"s":[50]},{"t":405,"s":[50]}],"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.400000029919,0.61568627451,0.964705942191,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":645,"st":-255,"bm":0},{"ddd":0,"ind":26,"ty":4,"nm":".grey900","cl":"grey900","parent":6,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[71,-82.917,0],"to":[0,-1.25,0],"ti":[0,1.25,0]},{"t":-199,"s":[71,-90.417,0]}],"ix":2,"l":2},"a":{"a":0,"k":[5.5,4,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":-239,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-0.07,1.5],[0,-1.5],[-0.047,1.5]],"c":false}]},{"t":-199,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3,1.5],[0,-1.5],[3,1.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5.5,4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-255,"op":-199,"st":-255,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
deleted file mode 100644
index 49c1c40..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_rear_landscape_base.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Landscape_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":-129,"s":[-67]},{"t":-29,"s":[0]}],"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
deleted file mode 100644
index 9ea0d35..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_base.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 3","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-2,"ix":10},"p":{"a":0,"k":[260.134,83.782,0],"ix":2,"l":2},"a":{"a":0,"k":[302.634,38.782,0],"ix":1,"l":2},"s":{"a":0,"k":[178,178,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.262,5.076],[0,0],[-0.424,-7.095],[-0.028,-0.225]],"o":[[3.269,-3.892],[-12.123,2.932],[0.015,0.234],[0.567,-0.034]],"v":[[9.232,0.652],[11.145,-6.746],[-11.412,6.046],[-11.346,6.746]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[241.281,55.033],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.565,-1.102],[3.269,-3.892],[0.566,-0.033],[-18.63,2.353],[-16.656,3.951],[9.004,6.546],[6.9,-2.19]],"o":[[0,0],[-4.262,5.076],[1.008,9.61],[14.171,-1.79],[-4.028,-10.569],[-4.156,1.703],[-4.392,1.392]],"v":[[-13.858,-7.546],[-15.771,-0.148],[-36.349,5.946],[-7.047,16.299],[36.349,9.142],[16.281,-17.051],[-0.156,-11.172]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[266.285,55.833],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"black circle matte 4","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"black circle matte 5","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey700","cl":"grey700","parent":16,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json b/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
deleted file mode 100644
index f2b2593..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_rear_portrait_reverse_base.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"BiometricPrompt_Rear_Portrait_Reverse_Base_Foldable","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 18","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[169.478,169.749,0],"ix":2,"l":2},"a":{"a":0,"k":[-48.123,-30.19,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":13,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"black circle matte","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey904","cl":"grey904","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,35.536,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.552,0.087],[0,0]],"o":[[0,0],[0,-3.287],[0,0],[0,0]],"v":[[-2.301,8.869],[-2.301,-3.772],[2.301,-9.806],[2.301,9.806]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"black circle matte 2","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-62.577,-27.655,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,3.286],[0,0],[-2.552,0.086],[0,0]],"o":[[0,0],[0,-3.286],[0,0],[-2.552,-0.086]],"v":[[-2.301,16.282],[-2.301,-16.281],[2.301,-22.313],[2.301,22.313]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"black circle matte 3","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-75.352,41.307,0],"ix":2,"l":2},"a":{"a":0,"k":[94.648,211.307,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.72,-5.642],[0,0],[-9.394,-0.562],[-0.298,-0.038]],"o":[[-5.153,4.329],[3.882,-16.05],[0.31,0.019],[-0.044,0.75]],"v":[[0.863,12.222],[-8.931,14.755],[8.005,-15.108],[8.931,-15.021]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[81.486,130.081],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.459,6.045],[-5.153,4.329],[-0.044,0.75],[3.116,-24.664],[5.23,-22.052],[8.666,11.92],[-2.9,9.135]],"o":[[0,0],[6.72,-5.642],[12.723,1.335],[-2.369,18.762],[-13.993,-5.333],[2.255,-5.502],[1.843,-5.815]],"v":[[-9.99,-18.348],[-0.196,-20.881],[7.872,-48.124],[21.578,-9.331],[12.104,48.124],[-22.574,21.555],[-14.791,-0.206]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[82.545,163.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"black circle matte 4","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey903","cl":"grey903","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-18.345,-92.442,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[24.07,0],[0,0],[-8.27,0],[0,0]],"o":[[0,0],[0,8.269],[0,0],[-14.024,-17.379]],"v":[[-29.778,-14.252],[-29.778,-0.721],[-14.805,14.252],[29.778,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"black circle matte 5","parent":13,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey902","cl":"grey902","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-15.947,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[154.053,139.81,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.3,0.367],[0,0],[-2.364,0.157],[0,0]],"o":[[0,0],[2.3,-0.367],[0,0],[-2.364,-0.157]],"v":[[-3.5,75.533],[-3.5,-75.533],[3.5,-76.312],[3.5,76.312]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[113.225,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,8.269],[0,0],[2.181,-0.187],[0,0],[-2.23,0],[0,42.252],[10.593,13.127],[0,0]],"o":[[0,0],[-2.23,0],[0,0],[2.181,0.187],[42.252,0],[0,-18.182],[0,0],[-8.27,0]],"v":[[-34.946,-62.973],[-34.946,-76.504],[-41.558,-76.201],[-41.558,76.201],[-34.946,76.504],[41.558,0],[24.61,-48],[-19.973,-48]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[156.824,139.81],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 5","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black 2","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-48.123,-30.19,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.167],"y":[0,0,0]},"t":-129,"s":[0,0,100]},{"t":-79,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-42.252,0],[0,42.252],[42.252,0],[0,-42.252]],"o":[[42.252,0],[0,-42.252],[-42.252,0],[0,42.252]],"v":[[0,76.504],[76.504,0],[0,-76.504],[-76.504,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".grey700","cl":"grey700","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-56.481,-59.936,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.767,0],[0,0],[0,-3.767],[0,0],[-3.767,0],[0,0],[0,3.767],[0,0]],"o":[[0,0],[-3.767,0],[0,0],[0,3.767],[0,0],[3.767,0],[0,0],[0,-3.767]],"v":[[46.055,-14.479],[-46.056,-14.479],[-52.876,-7.659],[-52.876,7.658],[-46.056,14.479],[46.055,14.479],[52.876,7.658],[52.876,-7.659]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".grey901","cl":"grey901","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.485,2.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.184,0],[0,0],[0,0],[0,0],[0,-4.375]],"o":[[0,4.184],[0,0],[0,0],[0,0],[4.375,0],[0,0]],"v":[[114.116,92.129],[106.54,99.705],[7.788,99.705],[7.788,-99.704],[106.161,-99.704],[114.116,-91.749]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.707,0],[0,0],[1.894,-1.05],[0.886,0.346],[0,0],[2.166,0],[0,0],[0,-5.707],[0,0],[0,-1.46],[0,0],[-1.133,-0.038],[0,0],[0,-1.459],[0,0],[-1.133,-0.038],[0,0],[-5.708,0],[0,0],[-1.894,1.05],[-0.846,-0.289],[0,0],[-2.166,0],[0,0],[0,5.706],[0,0]],"o":[[0,0],[-2.166,0],[-0.883,0.354],[0,0],[-1.895,-1.05],[0,0],[-5.708,0],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[-1.133,0.038],[0,0],[0,1.46],[0,0],[0,5.707],[0,0],[2.165,0],[0.833,-0.334],[0,0],[1.894,1.05],[0,0],[5.707,0],[0,0],[0,-5.707]],"v":[[106.16,-102.082],[8.455,-102.082],[2.265,-100.48],[-0.488,-100.468],[-0.519,-100.48],[-6.71,-102.082],[-104.116,-102.082],[-114.45,-91.748],[-114.45,-36.119],[-116.494,-33.44],[-116.494,-18.979],[-114.45,-16.3],[-114.45,-0.877],[-116.494,1.802],[-116.494,28.704],[-114.45,31.383],[-114.45,91.749],[-104.116,102.083],[-6.495,102.083],[-0.305,100.481],[2.294,100.425],[2.395,100.481],[9.872,102.083],[106.161,102.083],[116.494,91.75],[116.494,-91.748]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.529411792755,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-189,"op":711,"st":-189,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint.json b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint.json
new file mode 100644
index 0000000..fa6fc07
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_180.json
new file mode 100755
index 0000000..688ed7b
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,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":170,"s":[-43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[-43.5,43.5,100]},{"t":260,"s":[-64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_270.json
new file mode 100644
index 0000000..477c489
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_90.json
new file mode 100644
index 0000000..9e6b422
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_fingerprint_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_success.json b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_success.json
new file mode 100644
index 0000000..348e25d
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_success.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"Error-to-success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".red400","cl":"red400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[43.5,43.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"green400","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".green400","cl":"green400","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".grey400","cl":"grey400","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".grey600","cl":"grey600","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".grey600","cl":"grey600","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Nail","parent":17,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Matte","parent":6,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Finger","parent":6,"tt":1,"tp":16,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".black","cl":"black","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue401","cl":"blue401","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":6,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_unlock.json b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_unlock.json
new file mode 100644
index 0000000..ab52490
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_error_to_unlock.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-Unlock_0","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[74.259,78.552,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[44,44,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":244,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":259,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":94,"op":994,"st":94,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[74.259,78.552,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[44,44,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":94,"op":994,"st":94,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[43.5,43.5,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".red400","cl":"red400","parent":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","parent":5,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[{"tm":248,"cm":"","dr":0},{"tm":256,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_authenticating.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_authenticating.json
new file mode 100644
index 0000000..9db2543
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_authenticating.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":301,"w":150,"h":150,"nm":"BM_Prompt","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[64,64,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Nail","parent":8,"tt":1,"tp":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error.json
new file mode 100644
index 0000000..603760f
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_180.json
new file mode 100755
index 0000000..4e66cfa
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,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":170,"s":[-43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[-43.5,43.5,100]},{"t":260,"s":[-64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_270.json
new file mode 100644
index 0000000..a1d2251
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_90.json
new file mode 100644
index 0000000..59dc0d4
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_error_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success.json
new file mode 100644
index 0000000..e2c8500
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_180.json
new file mode 100755
index 0000000..21ee20d
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,100]},{"t":170,"s":[-43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_270.json
new file mode 100644
index 0000000..4c1a809
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_90.json
new file mode 100644
index 0000000..51e17f7
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_success_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock.json
new file mode 100644
index 0000000..c4db1e4
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_180.json
new file mode 100755
index 0000000..56cd2ad
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[-101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[-101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,100]},{"t":170,"s":[-43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_270.json
new file mode 100644
index 0000000..30bb2d9
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[8.166,1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[8.166,1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_90.json
new file mode 100644
index 0000000..f9d13a6
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_fingerprint_to_unlock_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-8.166,-1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-8.166,-1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint.json
new file mode 100644
index 0000000..688ed7b
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,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":170,"s":[-43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[-43.5,43.5,100]},{"t":260,"s":[-64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_180.json
new file mode 100644
index 0000000..fa6fc07
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_270.json
new file mode 100644
index 0000000..9e6b422
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_90.json
new file mode 100644
index 0000000..477c489
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_error_to_fingerprint_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":421,"w":150,"h":150,"nm":"BM_Prompt_Error-to-FP_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_authenticating.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_authenticating.json
new file mode 100755
index 0000000..c688037
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_authenticating.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":301,"w":150,"h":150,"nm":"BM_Prompt_180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-64,64,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Nail","parent":8,"tt":1,"tp":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue401","cl":"blue401","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error.json
new file mode 100644
index 0000000..4e66cfa
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,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":170,"s":[-43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[-43.5,43.5,100]},{"t":260,"s":[-64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_180.json
new file mode 100644
index 0000000..603760f
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_270.json
new file mode 100644
index 0000000..59dc0d4
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_90.json
new file mode 100644
index 0000000..a1d2251
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_error_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Error_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,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":170,"s":[43.5,43.5,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":240,"s":[43.5,43.5,100]},{"t":260,"s":[64,64,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red400","cl":"red400","parent":4,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,8.484,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.91,0.191,0],"ix":1,"l":2},"s":{"a":0,"k":[229.885,229.885,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,-7.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red400","cl":"red400","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-3.889,9.487,0],"ix":2,"l":2},"a":{"a":0,"k":[-3.889,7.487,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[4,4],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333396912,0.403921604156,0.360784322023,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.91,7.491],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red400","cl":"red400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.933333333333,0.403921568627,0.360784313725,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":170,"s":[6]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":240,"s":[6]},{"t":260,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Matte 3","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Nail 2","parent":11,"tt":1,"tp":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Matte 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Finger 2","parent":1,"tt":1,"tp":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[43]},{"t":365,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":320,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":365,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":428,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Nail","parent":15,"tt":1,"tp":12,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":14,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":159,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":320,"s":[50]},{"t":390,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":290,"s":[0,0,100]},{"t":380,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":290,"op":391,"st":290,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":290,"s":[50]},{"t":360,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":260,"s":[0,0,100]},{"t":350,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":260,"op":391,"st":260,"ct":1,"bm":0},{"ddd":0,"ind":20,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":131,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":21,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":131,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":22,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":248,"s":[0]},{"t":256,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success.json
new file mode 100644
index 0000000..21ee20d
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,100]},{"t":170,"s":[-43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_180.json
new file mode 100644
index 0000000..e2c8500
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_270.json
new file mode 100644
index 0000000..51e17f7
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_90.json
new file mode 100644
index 0000000..4c1a809
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_success_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":211,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Success_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"green400","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":150,"s":[0]},{"t":180,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":150,"op":1041,"st":141,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"t":166,"s":[100]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey400","cl":"grey400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey600","cl":"grey600","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Nail","parent":10,"tt":1,"tp":7,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Matte","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Finger","parent":1,"tt":1,"tp":9,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.833,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":".blue400","cl":"blue400","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock.json
new file mode 100644
index 0000000..56cd2ad
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_left","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[-101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[-101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[-64,64,100]},{"t":170,"s":[-43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_180.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_180.json
new file mode 100644
index 0000000..c4db1e4
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_180.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_0and180","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_270.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_270.json
new file mode 100644
index 0000000..f9d13a6
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_270.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_90","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-8.166,-1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[-8.166,-1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_90.json b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_90.json
new file mode 100644
index 0000000..30bb2d9
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_rear_display_fingerprint_to_unlock_90.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":181,"w":150,"h":150,"nm":"BM_Prompt_FP-to-Unlock_270","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[8.166,1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[8.166,1.704,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_sfps_unlock_to_success.json b/packages/SystemUI/res/raw/biometricprompt_sfps_unlock_to_success.json
new file mode 100644
index 0000000..141ebb7
--- /dev/null
+++ b/packages/SystemUI/res/raw/biometricprompt_sfps_unlock_to_success.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":240,"op":361,"w":150,"h":150,"nm":"BM_Prompt_FP_to_Unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue403","cl":"blue403","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.4,"y":0},"t":150,"s":[{"i":[[0,0],[0,0],[0.938,1.057],[1.355,0],[0.938,-1.057],[0,-1.527],[0,0],[-1.512,1.674],[-2.111,0],[-1.512,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[-0.938,-1.057],[-1.329,0],[-0.938,1.057],[0,0],[0,-2.408],[1.486,-1.703],[2.137,0],[1.486,1.674],[0,0],[0,0]],"v":[[-0.17,-6.078],[-0.17,-9.822],[-1.577,-13.699],[-5.017,-15.284],[-8.419,-13.699],[-9.826,-9.822],[-12.68,-9.822],[-10.412,-15.945],[-5.017,-18.5],[0.456,-15.945],[2.684,-9.822],[2.684,-6.078]],"c":true}]},{"t":165,"s":[{"i":[[0,0],[0,0],[-1.069,1.057],[-1.545,0],[-1.069,-1.057],[0,-1.527],[0,0],[1.723,1.674],[2.406,0],[1.723,-1.703],[0,-2.408],[0,0]],"o":[[0,0],[0,-1.527],[1.069,-1.057],[1.515,0],[1.069,1.057],[0,0],[0,-2.408],[-1.693,-1.703],[-2.436,0],[-1.693,1.674],[0,0],[0,0]],"v":[[2.74,-6.079],[2.74,-9.823],[4.345,-13.699],[8.266,-15.285],[12.143,-13.699],[13.747,-9.823],[17,-9.823],[14.415,-15.945],[8.266,-18.5],[2.028,-15.945],[-0.512,-9.823],[-0.512,-6.079]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue402","cl":"blue402","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":154,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.704,8.166,0],"ix":2,"l":2},"a":{"a":0,"k":[-5.873,13.309,0],"ix":1,"l":2},"s":{"a":0,"k":[101.149,101.149,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.266,15.285],[-13.747,15.285],[-13.747,-2.863],[8.266,-2.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.683,-0.675],[0.98,0],[0.683,0.675],[0,0.94],[-0.683,0.705],[-0.98,0],[-0.683,-0.705],[0,-0.94]],"o":[[-0.683,0.675],[-0.98,0],[-0.683,-0.675],[0,-0.94],[0.683,-0.705],[0.98,0],[0.683,0.705],[0,0.94]],"v":[[-0.245,8.677],[-2.74,9.69],[-5.236,8.677],[-6.261,6.255],[-5.236,3.788],[-2.74,2.731],[-0.245,3.788],[0.78,6.255]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.624,-0.646],[-0.891,0],[0,0],[-0.624,0.617],[0,0.881],[0,0],[0.654,0.617],[0.891,0],[0,0],[0,0],[0,0],[0.654,-0.646],[0,-0.881],[0,0]],"o":[[0.654,0.617],[0,0],[0.891,0],[0.654,-0.646],[0,0],[0,-0.881],[-0.624,-0.646],[0,0],[0,0],[0,0],[-0.891,0],[-0.624,0.617],[0,0],[0,0.881]],"v":[[-16.064,17.575],[-13.747,18.5],[8.266,18.5],[10.539,17.575],[11.519,15.285],[11.519,-2.863],[10.539,-5.11],[8.266,-6.079],[2.74,-6.079],[-0.512,-6.079],[-13.747,-6.079],[-16.064,-5.11],[-17,-2.863],[-17,15.285]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[214.286,214.286],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"Null 29","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75,75,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":150,"s":[64,64,100]},{"t":170,"s":[43.5,43.5,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"green400","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[71.264,71.264,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":240,"s":[0]},{"t":270,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":240,"op":1131,"st":231,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green400","cl":"green400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[0]},{"t":248,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.356862745098,0.725490196078,0.454901960784,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":982,"st":82,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":166,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":240,"s":[100]},{"t":248,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey400","cl":"grey400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":158,"s":[100]},{"t":166,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.741176486015,0.75686275959,0.776470601559,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":150,"s":[4]},{"t":170,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.422,1.727,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.148],[0,0],[2.171,0.019],[0,0]],"o":[[0,0],[0,2.148],[0,0],[2.171,0.084]],"v":[[1.504,-10.64],[1.504,10.641],[-2.285,14.585],[-2.285,-14.582]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey600","cl":"grey600","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.73,0.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.503,-0.24],[0,0],[1.545,-0.103],[0,0]],"o":[[0,0],[-1.503,0.24],[0,0],[1.545,0.103]],"v":[[2.287,-49.365],[2.287,49.365],[-2.287,49.875],[-2.287,-49.875]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.501960813999,0.525490224361,0.54509806633,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Nail","parent":14,"tt":1,"tp":11,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.413,-32.45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.537,-10.49],[3.914,3.121],[0.029,0.49],[-0.203,0.012]],"o":[[0,0],[-5.309,-4.235],[0.195,-0.025],[6.139,-0.367]],"v":[[5.837,9.643],[-1.02,8.379],[-5.837,-9.817],[-5.232,-9.874]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.792156875134,0.454901963472,0.376470595598,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Matte","parent":3,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-27.614,0],[0,27.614],[27.614,0],[0,-27.614]],"o":[[27.614,0],[0,-27.614],[-27.614,0],[0,27.614]],"v":[[0,50],[50,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Finger","parent":3,"tt":1,"tp":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[43]},{"t":105,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":60,"s":[53.57,92.052,0],"to":[-14.604,-5.758,0],"ti":[0.932,11.227,0]},{"t":105,"s":[29.227,68.052,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-0.948,45.138,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.392,-3.688],[0,0],[-1.205,-3.8],[-1.473,-3.596],[9.145,-3.485],[1.549,12.262],[-8.315,0.872]],"o":[[3.368,2.829],[0.954,3.951],[1.895,5.97],[-5.664,7.791],[-3.418,-14.412],[-2.036,-16.119],[0.029,0.49]],"v":[[-0.286,-13.646],[6.116,-11.991],[9.253,-0.135],[15.167,17.416],[-8.324,31.452],[-14.517,-6.098],[-5.559,-31.452]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.713725507259,0.384313732386,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-11.782,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-1.716,3.431],[1.716,0],[-1.716,-3.431]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-13.268,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,8.054],[-8.054,0]],"o":[[-8.054,0],[0,-8.054],[0,0]],"v":[[7.292,14.583],[-7.292,0],[7.292,-14.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":".blue401","cl":"blue401","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":60,"s":[50]},{"t":130,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"t":120,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":930,"st":30,"ct":1,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":".blue400","cl":"blue400","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":30,"s":[50]},{"t":100,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-2.33,1.408,0],"ix":2,"l":2},"a":{"a":0,"k":[22.5,-0.242,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.667],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":90,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,16.658],[-16.658,0]],"o":[[-16.658,0],[0,-16.658],[0,0]],"v":[[15.081,30.161],[-15.081,0],[15.081,-30.161]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":".black","cl":"black","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[100]},{"t":158,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-31.25,-2.604],[0,27.614],[0,0],[0,-27.614]],"o":[[0,-0.13],[0,-27.614],[-27.344,-0.651],[0,27.614]],"v":[[0,50],[1.172,0],[0,-50],[-50,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[150,150],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":900,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_landscape.json b/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_landscape.json
deleted file mode 100644
index 034ac87..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_landscape.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_error_to_fingerprint_landscape","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short_For_ErrorToFingerprint","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short_For_ErrorToFingerprint","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,77.667,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":792,"st":-108,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_portrait_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_portrait_bottomright.json
deleted file mode 100644
index e5cc565e..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_portrait_bottomright.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_error_to_fingerprint_portrait_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short_For_ErrorToFingerprint","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short_For_ErrorToFingerprint","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":792,"st":-108,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_portrait_topleft.json b/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_portrait_topleft.json
deleted file mode 100644
index b082265..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_fingerprint_portrait_topleft.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_error_to_fingerprint_portrait_topleft","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short_For_ErrorToFingerprint","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 12","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short_For_ErrorToFingerprint","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":792,"st":-108,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_landscape.json b/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_landscape.json
deleted file mode 100644
index befe3bb..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_landscape.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_error_to_success_landscape","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_ErrorToSuccess","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":257,"s":[100]},{"t":277,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":108,"op":1280,"st":108,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":112,"s":[0]},{"t":122,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":112,"op":1012,"st":112,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":257,"s":[100]},{"t":277,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":108,"op":1280,"st":108,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_ErrorToSuccess","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,77.667,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":792,"st":-108,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_portrait_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_portrait_bottomright.json
deleted file mode 100644
index d75b335..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_portrait_bottomright.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_error_to_success_portrait_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_ErrorToSuccess","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":257,"s":[100]},{"t":277,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":108,"op":1280,"st":108,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":112,"s":[0]},{"t":122,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":112,"op":1012,"st":112,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":257,"s":[100]},{"t":277,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":108,"op":1280,"st":108,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 13","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_ErrorToSuccess","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":792,"st":-108,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_portrait_topleft.json b/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_portrait_topleft.json
deleted file mode 100644
index e6b2db1..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_error_to_success_portrait_topleft.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_error_to_success_portrait_topleft","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_ErrorToSuccess","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":257,"s":[100]},{"t":277,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":108,"op":1280,"st":108,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":112,"s":[0]},{"t":122,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":112,"op":1012,"st":112,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":257,"s":[100]},{"t":277,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":108,"op":1280,"st":108,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 13","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_ErrorToSuccess","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":792,"st":-108,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_error_portrait_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_error_portrait_bottomright.json
deleted file mode 100644
index 0da143c..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_error_portrait_bottomright.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_fingerprint_to_error_portrait_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 14","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":830,"st":-70,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_error_portrait_topleft.json b/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_error_portrait_topleft.json
deleted file mode 100644
index 15457c7..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_error_portrait_topleft.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_fingerprint_to_error_portrait_topleft","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 14","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":830,"st":-70,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_landscape.json b/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_landscape.json
deleted file mode 100644
index f39894b..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_landscape.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_fingerprint_to_success_landscape","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[170,77.667,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":661,"st":-239,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_portrait_bottomright.json b/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_portrait_bottomright.json
deleted file mode 100644
index 6d4f4e2..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_portrait_bottomright.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_fingerprint_to_success_portrait_bottomright","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 15","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":661,"st":-239,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_portrait_topleft.json b/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_portrait_topleft.json
deleted file mode 100644
index b7bb0d5..0000000
--- a/packages/SystemUI/res/raw/biometricprompt_symbol_fingerprint_to_success_portrait_topleft.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":340,"h":340,"nm":"biometricprompt_symbol_fingerprint_to_success_portrait_topleft","ddd":0,"assets":[{"id":"comp_0","nm":"Fingerprint_Animation_ForConfirm_Short","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"circle fill 5","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-52.056,-2.445],[-17.306,32.25],[52.194,-37.194]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"t":249,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":239,"op":1139,"st":239,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green400","cl":"green400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":239,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":249,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":388,"s":[100]},{"t":408,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.450980392157,0.709803921569,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":239,"op":1411,"st":239,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"circle fill 3","td":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".white","cl":"white","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":80,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.8,"y":0},"t":108,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,-49.945],[-0.056,14.056]],"c":false}]},{"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.111,14.055],[-0.056,14.056]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-0.056,35.389],[-0.111,50.111]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0.049,42.698],"ix":2},"a":{"a":0,"k":[0.049,42.698],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":80,"s":[0,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":90,"s":[100,100]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":108,"s":[100,100]},{"t":118,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":80,"op":980,"st":80,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".red400","cl":"red400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":80,"s":[0]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":108,"s":[100]},{"t":118,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,62.45],[-62.45,0],[0,-62.45],[62.45,0]],"o":[[0,-62.45],[62.45,0],[0,62.45],[-62.45,0]],"v":[[-113.08,0],[0,-113.08],[113.08,0],[0,113.08]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[206,150],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333393172,0.403921598547,0.360784313725,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":80,"op":1252,"st":80,"bm":0},{"ddd":0,"ind":7,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0.333},"t":85,"s":[209.333,136.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":440,"s":[209.333,136.333,0],"to":[1.944,2.278,0],"ti":[3.056,-2.278,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":450,"s":[221,150,0],"to":[-3.056,2.278,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":460,"s":[191,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":470,"s":[221,150,0],"to":[0,0,0],"ti":[2.5,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":480,"s":[191,150,0],"to":[-2.5,0,0],"ti":[-2.5,0,0]},{"t":490,"s":[206,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[132,132,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 15","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[170,170,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":900,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Fingerprint_Animation_ForConfirm_Short","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[0,-92.333,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":412,"h":300,"ip":0,"op":661,"st":-239,"bm":0}],"markers":[{"tm":255,"cm":"","dr":0},{"tm":364,"cm":"","dr":0},{"tm":482,"cm":"","dr":0},{"tm":600,"cm":"","dr":0}]}
\ No newline at end of file
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/config.xml b/packages/SystemUI/res/values/config.xml
index c29c236..f0c8894 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1050,4 +1050,7 @@
<!-- Only applicable for dual shade - Allow Notifications/QS shade to anchor to the bottom. -->
<bool name="config_dualShadeAlignedToBottom">false</bool>
+
+ <!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] -->
+ <string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index eda7bb0..e5750d2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1729,10 +1729,18 @@
<dimen name="wallet_button_vertical_padding">8dp</dimen>
<!-- Ongoing activity chip -->
+ <!-- The activity chip side padding, used with the default phone icon. -->
<dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
+ <!-- The activity chip side padding, used with an icon that has embedded padding (e.g. if the icon comes from the notification's smallIcon field). If the icon has padding, the chip itself can have less padding. -->
+ <dimen name="ongoing_activity_chip_side_padding_for_embedded_padding_icon">6dp</dimen>
+ <!-- The icon size, used with the default phone icon. -->
<dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
- <!-- The padding between the icon and the text. -->
+ <!-- The icon size, used with an icon that has embedded padding. (If the icon has embedded padding, we need to make the whole icon larger so the icon itself doesn't look small.) -->
+ <dimen name="ongoing_activity_chip_embedded_padding_icon_size">22dp</dimen>
+ <!-- The padding between the icon and the text. Only used if the default phone icon is used. -->
<dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
+ <!-- The end padding for the timer text view. Only used if an embedded padding icon is used. -->
+ <dimen name="ongoing_activity_chip_text_end_padding_for_embedded_padding_icon">6dp</dimen>
<dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
<!-- Status bar user chip -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 212dae2..e4f900d 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -132,6 +132,7 @@
<!-- Status bar -->
<item type="id" name="status_bar_dot" />
+ <item type="id" name="ongoing_activity_chip_custom_icon" />
<!-- Default display cutout on the physical top of screen -->
<item type="id" name="display_cutout" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e590f15..a5fd5b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -432,8 +432,6 @@
<!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_logo">App logo</string>
- <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] -->
- <string-array name="biometric_dialog_package_names_for_logo_with_overrides" />
<!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
<string name="biometric_dialog_confirm">Confirm</string>
<!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
@@ -973,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>
@@ -1369,25 +1367,34 @@
<!-- Media projection that launched from 1P/3P apps -->
<!-- 1P/3P app media projection permission dialog title. [CHAR LIMIT=NONE] -->
- <string name="media_projection_entry_app_permission_dialog_title">Start recording or casting with <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>?</string>
+ <string name="media_projection_entry_app_permission_dialog_title">Share your screen with <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>?</string>
+
+ <!-- 1P/3P app media projection permission option for capturing just a single app [CHAR LIMIT=50] -->
+ <string name="media_projection_entry_app_permission_dialog_option_text_single_app">Share one app</string>
+ <!-- 1P/3P app media projection permission option for capturing the whole screen [CHAR LIMIT=50] -->
+ <string name="media_projection_entry_app_permission_dialog_option_text_entire_screen">Share entire screen</string>
<!-- 1P/3P app media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen">When you’re sharing, recording, or casting, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen">When you’re sharing your entire screen, anything on your screen is visible to <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
<!-- 1P/3P app media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
- <string name="media_projection_entry_app_permission_dialog_warning_single_app">When you’re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app">When you’re sharing an app, anything shown or played in that app is visible to <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
<!-- 1P/3P apps media projection permission button to continue with app selection or recording [CHAR LIMIT=60] -->
- <string name="media_projection_entry_app_permission_dialog_continue">Start</string>
+ <string name="media_projection_entry_app_permission_dialog_continue_entire_screen">Share screen</string>
<!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] -->
<string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string>
<!-- Casting that launched by SysUI (i.e. when there is no app name) -->
<!-- System casting media projection permission dialog title. [CHAR LIMIT=100] -->
- <string name="media_projection_entry_cast_permission_dialog_title">Start casting?</string>
+ <string name="media_projection_entry_cast_permission_dialog_title">Cast your screen?</string>
+ <!-- System casting media projection permission option for capturing just a single app [CHAR LIMIT=50] -->
+ <string name="media_projection_entry_cast_permission_dialog_option_text_single_app">Cast one app</string>
+ <!-- System casting media projection permission option for capturing the whole screen [CHAR LIMIT=50] -->
+ <string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen">Cast entire screen</string>
<!-- System casting media projection permission warning for capturing the whole screen when SysUI casting requests it. [CHAR LIMIT=350] -->
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen">When you’re casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen">When you’re casting your entire screen, anything on your screen is visible. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
<!-- System casting media projection permission warning for capturing a single app when SysUI casting requests it. [CHAR LIMIT=350] -->
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app">When you’re casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app">When you’re casting an app, anything shown or played in that app is visible. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
<!-- System casting media projection permission button to continue for SysUI casting. [CHAR LIMIT=60] -->
- <string name="media_projection_entry_cast_permission_dialog_continue">Start casting</string>
+ <string name="media_projection_entry_cast_permission_dialog_continue_entire_screen">Cast screen</string>
<!-- Other sharing (not recording nor casting) that launched by SysUI (currently not in use) -->
<!-- System sharing media projection permission dialog title. [CHAR LIMIT=100] -->
@@ -1398,6 +1405,8 @@
<string name="media_projection_entry_generic_permission_dialog_warning_single_app">When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
<!-- System sharing media projection permission button to continue. [CHAR LIMIT=60] -->
<string name="media_projection_entry_generic_permission_dialog_continue">Start</string>
+ <!-- System sharing media projection permission button to continue to the next step. [CHAR LIMIT=60] -->
+ <string name="media_projection_entry_generic_permission_dialog_continue_single_app">Next</string>
<!-- Task switcher notification -->
<!-- Task switcher notification text. [CHAR LIMIT=100] -->
@@ -3649,6 +3658,8 @@
hasn't typed in anything in the search box yet. The helper is a component that shows the
user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_search_placeholder">Search shortcuts</string>
+ <!-- Text shown when a search query didn't produce any results. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_no_search_results">No search results</string>
<!-- Content description of the icon that allows to collapse a keyboard shortcut helper category
panel. The helper is a component that shows the user which keyboard shortcuts they can
use. The helper shows shortcuts in categories, which can be collapsed or expanded.
@@ -3668,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] -->
@@ -3676,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>
@@ -3709,6 +3739,11 @@
<!-- Education toast text for All Apps [CHAR_LIMIT=100] -->
<string name="all_apps_edu_toast_content">To view all your apps, press the action key on your keyboard</string>
+ <!-- Title of the one line view of a redacted notification -->
+ <string name="redacted_notification_single_line_title">Redacted</string>
+ <!-- Main text of the one line view of a redacted notification -->
+ <string name="redacted_notification_single_line_text">Unlock to view</string>
+
<!-- Education notification title for Back [CHAR_LIMIT=100] -->
<string name="back_edu_notification_title">Use your touchpad to go back</string>
<!-- Education notification text for Back [CHAR_LIMIT=100] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c428705d..7fa7088 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -192,11 +192,10 @@
<style name="TextAppearance.AuthCredential.LogoDescription" parent="TextAppearance.Material3.LabelLarge" >
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
- <item name="android:ellipsize">marquee</item>
<item name="android:gravity">@integer/biometric_dialog_text_gravity</item>
- <item name="android:marqueeRepeatLimit">1</item>
- <item name="android:singleLine">true</item>
+ <item name="android:maxLines">1</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+ <item name="android:ellipsize">end</item>
</style>
<style name="TextAppearance.AuthCredential.Title" parent="TextAppearance.Material3.HeadlineSmall" >
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/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ca03a00..da270c0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -39,7 +39,6 @@
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.biometrics.shared.model.AuthenticationState
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
@@ -49,6 +48,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
@@ -85,7 +85,7 @@
* onAcquired in [FingerprintManager.EnrollmentCallback] and [FaceManager.EnrollmentCallback]
*/
private val authenticationState: Flow<AuthenticationState> =
- conflatedCallbackFlow {
+ callbackFlow {
val updateAuthenticationState = { state: AuthenticationState ->
Log.d(TAG, "authenticationState updated: $state")
trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
@@ -169,7 +169,9 @@
}
}
- updateAuthenticationState(AuthenticationState.Idle(AuthenticationReason.NotRunning))
+ updateAuthenticationState(
+ AuthenticationState.Idle(requestReason = AuthenticationReason.NotRunning)
+ )
biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
awaitClose {
biometricManager?.unregisterAuthenticationStateListener(
@@ -180,23 +182,32 @@
.distinctUntilChanged()
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
- override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ private val fingerprintAuthenticationState: Flow<AuthenticationState> =
authenticationState
.filter {
- it is AuthenticationState.Idle ||
- (it is AuthenticationState.Started &&
- it.biometricSourceType == BiometricSourceType.FINGERPRINT) ||
- (it is AuthenticationState.Stopped &&
- it.biometricSourceType == BiometricSourceType.FINGERPRINT)
+ it.biometricSourceType == null ||
+ it.biometricSourceType == BiometricSourceType.FINGERPRINT
}
+ .onEach { Log.d(TAG, "fingerprintAuthenticationState updated: $it") }
+
+ private val fingerprintRunningState: Flow<AuthenticationState> =
+ fingerprintAuthenticationState
+ .filter {
+ it is AuthenticationState.Idle ||
+ it is AuthenticationState.Started ||
+ it is AuthenticationState.Stopped
+ }
+ .onEach { Log.d(TAG, "fingerprintRunningState updated: $it") }
+
+ override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ fingerprintRunningState
.map { it.requestReason }
.onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") }
override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
- authenticationState
- .filterIsInstance<AuthenticationState.Acquired>()
- .filter { it.biometricSourceType == BiometricSourceType.FINGERPRINT }
- .map { AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo) }
+ fingerprintAuthenticationState.filterIsInstance<AuthenticationState.Acquired>().map {
+ AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo)
+ }
companion object {
private const val TAG = "BiometricStatusRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
index 5ceae36..81ea6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
@@ -27,6 +27,9 @@
* authentication.
*/
sealed interface AuthenticationState {
+ /** Indicates [BiometricSourceType] of authentication state update, null in idle auth state. */
+ val biometricSourceType: BiometricSourceType?
+
/**
* Indicates [AuthenticationReason] from [BiometricRequestConstants.RequestReason] for
* requesting auth
@@ -43,7 +46,7 @@
* message.
*/
data class Acquired(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason,
val acquiredInfo: Int
) : AuthenticationState
@@ -59,7 +62,7 @@
* @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
*/
data class Error(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
val errString: String?,
val errCode: Int,
override val requestReason: AuthenticationReason,
@@ -73,7 +76,7 @@
* @param userId The user id for the requested authentication
*/
data class Failed(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason,
val userId: Int
) : AuthenticationState
@@ -87,7 +90,7 @@
* @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
*/
data class Help(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
val helpString: String?,
val helpCode: Int,
override val requestReason: AuthenticationReason,
@@ -96,9 +99,13 @@
/**
* Authentication state when no auth is running
*
+ * @param biometricSourceType null
* @param requestReason [AuthenticationReason.NotRunning]
*/
- data class Idle(override val requestReason: AuthenticationReason) : AuthenticationState
+ data class Idle(
+ override val biometricSourceType: BiometricSourceType? = null,
+ override val requestReason: AuthenticationReason
+ ) : AuthenticationState
/**
* AuthenticationState when auth is started
@@ -107,7 +114,7 @@
* @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
*/
data class Started(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason
) : AuthenticationState
@@ -118,7 +125,7 @@
* @param requestReason [AuthenticationReason.NotRunning]
*/
data class Stopped(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason
) : AuthenticationState
@@ -131,7 +138,7 @@
* @param userId The user id for the requested authentication
*/
data class Succeeded(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
val isStrongBiometric: Boolean,
override val requestReason: AuthenticationReason,
val userId: Int
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 7f236f7..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
@@ -67,6 +67,7 @@
import kotlinx.coroutines.launch
private const val TAG = "BiometricViewBinder"
+private const val MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30
/** Top-most view binder for BiometricPrompt views. */
object BiometricViewBinder {
@@ -128,7 +129,6 @@
subtitleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
- val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
val iconSizeOverride =
@@ -184,19 +184,31 @@
// these do not change and need to be set before any size transitions
val modalities = viewModel.modalities.first()
- if (modalities.hasFingerprint) {
- /**
- * Load the given [rawResources] immediately so they are cached for use in the
- * [context].
- */
- val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
- for (res in rawResources) {
- LottieCompositionFactory.fromRawRes(view.context, res)
+ /**
+ * Load the given [rawResources] immediately so they are cached for use in the
+ * [context].
+ */
+ val rawResources =
+ if (modalities.hasFaceAndFingerprint) {
+ viewModel.iconViewModel.getCoexAssetsList(modalities.hasSfps)
+ } else if (modalities.hasFingerprintOnly) {
+ viewModel.iconViewModel.getFingerprintAssetsList(modalities.hasSfps)
+ } else if (modalities.hasFaceOnly) {
+ viewModel.iconViewModel.getFaceAssetsList()
+ } else {
+ listOf()
}
+
+ for (res in rawResources) {
+ LottieCompositionFactory.fromRawRes(view.context, res)
}
- logoView.setImageDrawable(viewModel.logo.first())
- logoDescriptionView.text = viewModel.logoDescription.first()
+ val logoInfo = viewModel.logoInfo.first()
+ logoView.setImageDrawable(logoInfo.first)
+ // The ellipsize effect on xml happens only when the TextView does not have any free
+ // space on the screen to show the text. So we need to manually truncate.
+ logoDescriptionView.text =
+ logoInfo.second?.ellipsize(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER)
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
@@ -263,7 +275,6 @@
if (!showWithoutIcon) {
PromptIconViewBinder.bind(
iconView,
- iconOverlayView,
iconSizeOverride,
viewModel,
)
@@ -387,10 +398,7 @@
else -> null
}
}
- .collect { onTouch ->
- iconOverlayView.setOnTouchListener(onTouch)
- iconView.setOnTouchListener(onTouch)
- }
+ .collect { onTouch -> iconView.setOnTouchListener(onTouch) }
}
// dismiss prompt when authenticated and confirmed
@@ -411,9 +419,6 @@
accessibilityManager.isTouchExplorationEnabled &&
modalities.hasUdfps
) {
- iconOverlayView.setOnClickListener {
- viewModel.confirmAuthenticated()
- }
iconView.setOnClickListener { viewModel.confirmAuthenticated() }
}
}
@@ -694,7 +699,12 @@
}
fun startTransitionToCredentialUI(isError: Boolean) {
- applicationScope.launch {
+ if (!constraintBp()) {
+ applicationScope.launch {
+ viewModel.onSwitchToCredential()
+ legacyCallback?.onUseDeviceCredential()
+ }
+ } else {
viewModel.onSwitchToCredential()
legacyCallback?.onUseDeviceCredential()
}
@@ -717,6 +727,9 @@
else -> ""
}
+private fun String.ellipsize(cutOffLength: Int) =
+ if (length <= cutOffLength) this else replaceRange(cutOffLength, length, "...")
+
private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index a87ee24..b9ec2de 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -167,16 +167,13 @@
fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) {
viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
- largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
largeConstraintSet.setVisibility(R.id.indicator, View.GONE)
largeConstraintSet.setVisibility(R.id.scrollView, View.GONE)
if (hideSensorIcon) {
smallConstraintSet.setVisibility(iconHolderView.id, View.GONE)
- smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
smallConstraintSet.setVisibility(R.id.indicator, View.GONE)
mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE)
- mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE)
mediumConstraintSet.setVisibility(R.id.indicator, View.GONE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 5e0e2bb..9e4aaaa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.binder
+import android.content.res.Resources
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -29,8 +30,8 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Quad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.sample
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -39,14 +40,10 @@
/** Sub-binder for [BiometricPromptLayout.iconView]. */
object PromptIconViewBinder {
- /**
- * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
- * [PromptIconViewModel].
- */
+ /** Binds [BiometricPromptLayout.iconView] to [PromptIconViewModel]. */
@JvmStatic
fun bind(
iconView: LottieAnimationView,
- iconOverlayView: LottieAnimationView,
iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
promptViewModel: PromptViewModel
) {
@@ -57,9 +54,6 @@
if (iconViewLayoutParamSizeOverride != null) {
iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
-
- iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
- iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
}
if (!constraintBp()) {
@@ -95,9 +89,6 @@
if (iconViewLayoutParamSizeOverride == null) {
iconView.layoutParams.width = iconSize.first
iconView.layoutParams.height = iconSize.second
-
- iconOverlayView.layoutParams.width = iconSize.first
- iconOverlayView.layoutParams.height = iconSize.second
}
}
}
@@ -109,18 +100,25 @@
combine(
viewModel.activeAuthType,
viewModel.shouldAnimateIconView,
+ viewModel.shouldLoopIconView,
viewModel.showingError,
- ::Triple
+ ::Quad
),
- ::toQuad
+ ::toQuint
)
- .collect { (iconAsset, activeAuthType, shouldAnimateIconView, showingError)
- ->
+ .collect {
+ (
+ iconAsset,
+ activeAuthType,
+ shouldAnimateIconView,
+ shouldLoopIconView,
+ showingError) ->
if (iconAsset != -1) {
iconView.updateAsset(
"iconAsset",
iconAsset,
shouldAnimateIconView,
+ shouldLoopIconView,
activeAuthType
)
viewModel.setPreviousIconWasError(showingError)
@@ -129,34 +127,7 @@
}
launch {
- viewModel.iconOverlayAsset
- .sample(
- combine(
- viewModel.shouldAnimateIconOverlay,
- viewModel.showingError,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
- if (iconOverlayAsset != -1) {
- iconOverlayView.updateAsset(
- "iconOverlayAsset",
- iconOverlayAsset,
- shouldAnimateIconOverlay,
- AuthType.Fingerprint
- )
- viewModel.setPreviousIconOverlayWasError(showingError)
- }
- }
- }
-
- launch {
- viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
- if (shouldFlipIconView) {
- iconView.rotation = 180f
- }
- }
+ viewModel.iconViewRotation.collect { rotation -> iconView.rotation = rotation }
}
launch {
@@ -171,97 +142,83 @@
}
}
-private val assetIdToString: Map<Int, String> =
- mapOf(
- // UDFPS assets
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie to
- "fingerprint_dialogue_error_to_fingerprint_lottie",
- R.raw.fingerprint_dialogue_error_to_success_lottie to
- "fingerprint_dialogue_error_to_success_lottie",
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie to
- "fingerprint_dialogue_fingerprint_to_error_lottie",
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie to
- "fingerprint_dialogue_fingerprint_to_success_lottie",
- // SFPS assets
- R.raw.biometricprompt_fingerprint_to_error_landscape to
- "biometricprompt_fingerprint_to_error_landscape",
- R.raw.biometricprompt_folded_base_bottomright to "biometricprompt_folded_base_bottomright",
- R.raw.biometricprompt_folded_base_default to "biometricprompt_folded_base_default",
- R.raw.biometricprompt_folded_base_topleft to "biometricprompt_folded_base_topleft",
- R.raw.biometricprompt_landscape_base to "biometricprompt_landscape_base",
- R.raw.biometricprompt_portrait_base_bottomright to
- "biometricprompt_portrait_base_bottomright",
- R.raw.biometricprompt_portrait_base_topleft to "biometricprompt_portrait_base_topleft",
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape to
- "biometricprompt_symbol_error_to_fingerprint_landscape",
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright to
- "biometricprompt_symbol_error_to_fingerprint_portrait_bottomright",
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft to
- "biometricprompt_symbol_error_to_fingerprint_portrait_topleft",
- R.raw.biometricprompt_symbol_error_to_success_landscape to
- "biometricprompt_symbol_error_to_success_landscape",
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright to
- "biometricprompt_symbol_error_to_success_portrait_bottomright",
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft to
- "biometricprompt_symbol_error_to_success_portrait_topleft",
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright to
- "biometricprompt_symbol_fingerprint_to_error_portrait_bottomright",
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft to
- "biometricprompt_symbol_fingerprint_to_error_portrait_topleft",
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape to
- "biometricprompt_symbol_fingerprint_to_success_landscape",
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright to
- "biometricprompt_symbol_fingerprint_to_success_portrait_bottomright",
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft to
- "biometricprompt_symbol_fingerprint_to_success_portrait_topleft",
- // Face assets
- R.raw.face_dialog_wink_from_dark to "face_dialog_wink_from_dark",
- R.raw.face_dialog_dark_to_checkmark to "face_dialog_dark_to_checkmark",
- R.raw.face_dialog_dark_to_error to "face_dialog_dark_to_error",
- R.raw.face_dialog_error_to_idle to "face_dialog_error_to_idle",
- R.raw.face_dialog_idle_static to "face_dialog_idle_static",
- R.raw.face_dialog_authenticating to "face_dialog_authenticating",
- // Co-ex assets
- R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie to
- "fingerprint_dialogue_unlocked_to_checkmark_success_lottie",
- R.raw.fingerprint_dialogue_error_to_unlock_lottie to
- "fingerprint_dialogue_error_to_unlock_lottie",
- R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie to
- "fingerprint_dialogue_fingerprint_to_unlock_lottie",
- )
-
-private fun getAssetNameFromId(id: Int): String {
- return assetIdToString.getOrDefault(id, "Asset $id not found")
-}
-
fun LottieAnimationView.updateAsset(
type: String,
asset: Int,
shouldAnimateIconView: Boolean,
+ shouldLoopIconView: Boolean,
activeAuthType: AuthType
) {
setFailureListener(type, asset, activeAuthType)
+ pauseAnimation()
setAnimation(asset)
- frame = 0
+ if (animatingFromSfpsAuthenticating(asset)) {
+ // Skipping to error / success / unlock segment of animation
+ setMinFrame(151)
+ } else {
+ frame = 0
+ }
if (shouldAnimateIconView) {
- if (asset == R.raw.face_dialog_authenticating) {
- loop(true)
- } else {
- loop(false)
- }
+ loop(shouldLoopIconView)
playAnimation()
}
LottieColorUtils.applyDynamicColors(context, this)
}
+private fun animatingFromSfpsAuthenticating(asset: Int): Boolean =
+ asset in sfpsFpToErrorAssets || asset in sfpsFpToUnlockAssets || asset in sfpsFpToSuccessAssets
+
+private val sfpsFpToErrorAssets: List<Int> =
+ listOf(
+ R.raw.biometricprompt_sfps_fingerprint_to_error,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_270,
+ )
+
+private val sfpsFpToUnlockAssets: List<Int> =
+ listOf(
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_270,
+ )
+
+private val sfpsFpToSuccessAssets: List<Int> =
+ listOf(
+ R.raw.biometricprompt_sfps_fingerprint_to_success,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_270,
+ )
+
private fun LottieAnimationView.setFailureListener(type: String, asset: Int, authType: AuthType) {
+ val assetName =
+ try {
+ context.resources.getResourceEntryName(asset)
+ } catch (e: Resources.NotFoundException) {
+ "Asset $asset not found"
+ }
+
setFailureListener { result: Throwable? ->
Log.d(
TAG,
"Collecting $type | " +
"activeAuthType = $authType | " +
"Invalid resource id: $asset, " +
- "name ${getAssetNameFromId(asset)}",
+ "name $assetName",
result
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index c089143..574c40d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -19,11 +19,8 @@
import android.annotation.RawRes
import android.content.res.Configuration
-import android.graphics.Rect
-import android.util.RotationUtils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.res.R
@@ -35,15 +32,11 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-/**
- * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
- */
-class PromptIconViewModel
-constructor(
+/** Models UI of [BiometricPromptLayout.iconView] */
+class PromptIconViewModel(
promptViewModel: PromptViewModel,
private val displayStateInteractor: DisplayStateInteractor,
promptSelectorInteractor: PromptSelectorInteractor,
- udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
/** Auth types for the UI to display. */
@@ -74,44 +67,16 @@
}
}
- val udfpsSensorBounds: Flow<Rect> =
- combine(
- udfpsOverlayInteractor.udfpsOverlayParams,
- displayStateInteractor.currentRotation
- ) { params, rotation ->
- val rotatedBounds = Rect(params.sensorBounds)
- RotationUtils.rotateBounds(
- rotatedBounds,
- params.naturalDisplayWidth,
- params.naturalDisplayHeight,
- rotation.ordinal
- )
- Rect(
- rotatedBounds.left,
- rotatedBounds.top,
- params.logicalDisplayWidth - rotatedBounds.right,
- params.logicalDisplayHeight - rotatedBounds.bottom
- )
- }
- .distinctUntilChanged()
-
/** Whether an error message is currently being shown. */
val showingError = promptViewModel.showingError
/** Whether the previous icon shown displayed an error. */
private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
- /** Whether the previous icon overlay shown displayed an error. */
- private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
fun setPreviousIconWasError(previousIconWasError: Boolean) {
_previousIconWasError.value = previousIconWasError
}
- fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
- _previousIconOverlayWasError.value = previousIconOverlayWasError
- }
-
val iconSize: Flow<Pair<Int, Int>> =
combine(
promptViewModel.position,
@@ -133,7 +98,6 @@
AuthType.Fingerprint ->
combine(
displayStateInteractor.currentRotation,
- displayStateInteractor.isFolded,
displayStateInteractor.isInRearDisplayMode,
promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
@@ -141,7 +105,6 @@
promptViewModel.showingError
) {
rotation: DisplayRotation,
- isFolded: Boolean,
isInRearDisplayMode: Boolean,
sensorType: FingerprintSensorType,
authState: PromptAuthState,
@@ -149,7 +112,13 @@
showingError: Boolean ->
when (sensorType) {
FingerprintSensorType.POWER_BUTTON ->
- getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ getSfpsIconViewAsset(
+ rotation,
+ isInRearDisplayMode,
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
else ->
getFingerprintIconViewAsset(
authState.isAuthenticated,
@@ -179,7 +148,6 @@
AuthType.Coex ->
combine(
displayStateInteractor.currentRotation,
- displayStateInteractor.isFolded,
displayStateInteractor.isInRearDisplayMode,
promptSelectorInteractor.fingerprintSensorType,
promptViewModel.isAuthenticated,
@@ -188,7 +156,6 @@
promptViewModel.showingError,
) {
rotation: DisplayRotation,
- isFolded: Boolean,
isInRearDisplayMode: Boolean,
sensorType: FingerprintSensorType,
authState: PromptAuthState,
@@ -197,7 +164,14 @@
showingError: Boolean ->
when (sensorType) {
FingerprintSensorType.POWER_BUTTON ->
- getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ getCoexSfpsIconViewAsset(
+ rotation,
+ isInRearDisplayMode,
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
else ->
getCoexIconViewAsset(
authState,
@@ -214,8 +188,8 @@
isAuthenticated: Boolean,
isAuthenticating: Boolean,
showingError: Boolean
- ): Int =
- if (isAuthenticated) {
+ ): Int {
+ return if (isAuthenticated) {
if (_previousIconWasError.value) {
R.raw.fingerprint_dialogue_error_to_success_lottie
} else {
@@ -232,39 +206,34 @@
} else {
-1
}
+ }
@RawRes
private fun getSfpsIconViewAsset(
rotation: DisplayRotation,
- isDeviceFolded: Boolean,
isInRearDisplayMode: Boolean,
- ): Int =
- when (rotation) {
- DisplayRotation.ROTATION_90 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_reverse_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_topleft
- } else {
- R.raw.biometricprompt_portrait_base_topleft
- }
- DisplayRotation.ROTATION_270 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_bottomright
- } else {
- R.raw.biometricprompt_portrait_base_bottomright
- }
- else ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_landscape_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_default
- } else {
- R.raw.biometricprompt_landscape_base
- }
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int {
+ return if (isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.biometricprompt_sfps_error_to_success
+ } else {
+ getSfpsAsset_fingerprintToSuccess(rotation, isInRearDisplayMode)
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ getSfpsAsset_errorToFingerprint(rotation, isInRearDisplayMode)
+ } else {
+ getSfpsAsset_fingerprintAuthenticating(isInRearDisplayMode)
+ }
+ } else if (showingError) {
+ getSfpsAsset_fingerprintToError(rotation, isInRearDisplayMode)
+ } else {
+ -1
}
+ }
@RawRes
private fun getFaceIconViewAsset(
@@ -272,8 +241,8 @@
isAuthenticating: Boolean,
isPendingConfirmation: Boolean,
showingError: Boolean
- ): Int =
- if (authState.isAuthenticated && isPendingConfirmation) {
+ ): Int {
+ return if (authState.isAuthenticated && isPendingConfirmation) {
R.raw.face_dialog_wink_from_dark
} else if (authState.isAuthenticated) {
R.raw.face_dialog_dark_to_checkmark
@@ -286,6 +255,7 @@
} else {
R.raw.face_dialog_idle_static
}
+ }
@RawRes
private fun getCoexIconViewAsset(
@@ -293,8 +263,8 @@
isAuthenticating: Boolean,
isPendingConfirmation: Boolean,
showingError: Boolean
- ): Int =
- if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ ): Int {
+ return if (authState.isAuthenticatedAndExplicitlyConfirmed) {
R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
} else if (isPendingConfirmation) {
if (_previousIconWasError.value) {
@@ -319,107 +289,43 @@
} else {
-1
}
-
- /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
- var iconOverlayAsset: Flow<Int> =
- activeAuthType.flatMapLatest { activeAuthType: AuthType ->
- when (activeAuthType) {
- AuthType.Fingerprint,
- AuthType.Coex ->
- combine(
- displayStateInteractor.currentRotation,
- promptSelectorInteractor.fingerprintSensorType,
- promptViewModel.isAuthenticated,
- promptViewModel.isAuthenticating,
- promptViewModel.showingError
- ) {
- rotation: DisplayRotation,
- sensorType: FingerprintSensorType,
- authState: PromptAuthState,
- isAuthenticating: Boolean,
- showingError: Boolean ->
- when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- getSfpsIconOverlayAsset(
- rotation,
- authState.isAuthenticated,
- isAuthenticating,
- showingError
- )
- else -> -1
- }
- }
- AuthType.Face -> flowOf(-1)
- }
- }
+ }
@RawRes
- private fun getSfpsIconOverlayAsset(
+ private fun getCoexSfpsIconViewAsset(
rotation: DisplayRotation,
- isAuthenticated: Boolean,
+ isInRearDisplayMode: Boolean,
+ authState: PromptAuthState,
isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
showingError: Boolean
- ): Int =
- if (isAuthenticated) {
- if (_previousIconOverlayWasError.value) {
- when (rotation) {
- DisplayRotation.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- DisplayRotation.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
- DisplayRotation.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
- }
+ ): Int {
+ return if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.raw.biometricprompt_sfps_unlock_to_success
+ } else if (isPendingConfirmation) {
+ if (_previousIconWasError.value) {
+ R.raw.biometricprompt_sfps_error_to_unlock
} else {
- when (rotation) {
- DisplayRotation.ROTATION_0 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- DisplayRotation.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- DisplayRotation.ROTATION_180 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
- }
+ getSfpsAsset_fingerprintToUnlock(rotation, isInRearDisplayMode)
+ }
+ } else if (authState.isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.biometricprompt_sfps_error_to_success
+ } else {
+ getSfpsAsset_fingerprintToSuccess(rotation, isInRearDisplayMode)
}
} else if (isAuthenticating) {
- if (_previousIconOverlayWasError.value) {
- when (rotation) {
- DisplayRotation.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- DisplayRotation.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
- DisplayRotation.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
- }
+ if (_previousIconWasError.value) {
+ getSfpsAsset_errorToFingerprint(rotation, isInRearDisplayMode)
} else {
- when (rotation) {
- DisplayRotation.ROTATION_0 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- DisplayRotation.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- DisplayRotation.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- }
+ getSfpsAsset_fingerprintAuthenticating(isInRearDisplayMode)
}
} else if (showingError) {
- when (rotation) {
- DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- DisplayRotation.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- DisplayRotation.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- }
+ getSfpsAsset_fingerprintToError(rotation, isInRearDisplayMode)
} else {
-1
}
+ }
/** Content description for iconView */
val contentDescriptionId: Flow<Int> =
@@ -516,12 +422,7 @@
isAuthenticating: Boolean,
showingError: Boolean ->
when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- shouldAnimateSfpsIconView(
- authState.isAuthenticated,
- isAuthenticating,
- showingError
- )
+ FingerprintSensorType.POWER_BUTTON -> true
else ->
shouldAnimateFingerprintIconView(
authState.isAuthenticated,
@@ -556,12 +457,7 @@
isPendingConfirmation: Boolean,
showingError: Boolean ->
when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- shouldAnimateSfpsIconView(
- authState.isAuthenticated,
- isAuthenticating,
- showingError
- )
+ FingerprintSensorType.POWER_BUTTON -> true
else ->
shouldAnimateCoexIconView(
authState.isAuthenticated,
@@ -574,18 +470,22 @@
}
}
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be looping. */
+ val shouldLoopIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> flowOf(false)
+ AuthType.Face -> promptViewModel.isAuthenticating
+ }
+ }
+
private fun shouldAnimateFingerprintIconView(
isAuthenticated: Boolean,
isAuthenticating: Boolean,
showingError: Boolean
) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
- private fun shouldAnimateSfpsIconView(
- isAuthenticated: Boolean,
- isAuthenticating: Boolean,
- showingError: Boolean
- ) = isAuthenticated || isAuthenticating || showingError
-
private fun shouldAnimateCoexIconView(
isAuthenticated: Boolean,
isAuthenticating: Boolean,
@@ -597,89 +497,123 @@
isAuthenticated ||
showingError
- /** Whether the current iconOverlayAsset animation should be playing. */
- val shouldAnimateIconOverlay: Flow<Boolean> =
- activeAuthType.flatMapLatest { activeAuthType: AuthType ->
- when (activeAuthType) {
- AuthType.Fingerprint,
- AuthType.Coex ->
- combine(
- promptSelectorInteractor.fingerprintSensorType,
- promptViewModel.isAuthenticated,
- promptViewModel.isAuthenticating,
- promptViewModel.showingError
- ) {
- sensorType: FingerprintSensorType,
- authState: PromptAuthState,
- isAuthenticating: Boolean,
- showingError: Boolean ->
- when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- shouldAnimateSfpsIconOverlay(
- authState.isAuthenticated,
- isAuthenticating,
- showingError
- )
- else -> false
- }
- }
- AuthType.Face -> flowOf(false)
+ /* Used to rotate the iconView for assets reused across rotations. */
+ val iconViewRotation: Flow<Float> =
+ combine(iconAsset, displayStateInteractor.currentRotation) {
+ icon: Int,
+ rotation: DisplayRotation ->
+ if (assetReusedAcrossRotations(icon)) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> 0f
+ DisplayRotation.ROTATION_90 -> 270f
+ DisplayRotation.ROTATION_180 -> 180f
+ DisplayRotation.ROTATION_270 -> 90f
+ }
+ } else {
+ 0f
}
}
- private fun shouldAnimateSfpsIconOverlay(
- isAuthenticated: Boolean,
- isAuthenticating: Boolean,
- showingError: Boolean
- ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+ private fun assetReusedAcrossRotations(asset: Int): Boolean {
+ return asset in assetsReusedAcrossRotations
+ }
- /** Whether the iconView should be flipped due to a device using reverse default rotation . */
- val shouldFlipIconView: Flow<Boolean> =
- activeAuthType.flatMapLatest { activeAuthType: AuthType ->
- when (activeAuthType) {
- AuthType.Fingerprint,
- AuthType.Coex ->
- combine(
- promptSelectorInteractor.fingerprintSensorType,
- displayStateInteractor.currentRotation
- ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
- when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- (rotation == DisplayRotation.ROTATION_180)
- else -> false
- }
- }
- AuthType.Face -> flowOf(false)
- }
- }
+ private val assetsReusedAcrossRotations: List<Int> =
+ listOf(
+ R.raw.biometricprompt_sfps_fingerprint_authenticating,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating
+ )
/** Called on configuration changes */
fun onConfigurationChanged(newConfig: Configuration) {
displayStateInteractor.onConfigurationChanged(newConfig)
}
- /** iconView assets for caching */
- fun getRawAssets(hasSfps: Boolean): List<Int> {
- return if (hasSfps) {
+ /** Coex iconView assets for caching */
+ fun getCoexAssetsList(hasSfps: Boolean): List<Int> =
+ if (hasSfps) {
listOf(
- R.raw.biometricprompt_fingerprint_to_error_landscape,
- R.raw.biometricprompt_folded_base_bottomright,
- R.raw.biometricprompt_folded_base_default,
- R.raw.biometricprompt_folded_base_topleft,
- R.raw.biometricprompt_landscape_base,
- R.raw.biometricprompt_portrait_base_bottomright,
- R.raw.biometricprompt_portrait_base_topleft,
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
- R.raw.biometricprompt_symbol_error_to_success_landscape,
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ R.raw.biometricprompt_sfps_fingerprint_authenticating,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating,
+ R.raw.biometricprompt_sfps_error_to_unlock,
+ R.raw.biometricprompt_sfps_error_to_success,
+ R.raw.biometricprompt_sfps_fingerprint_to_error,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_270,
+ R.raw.biometricprompt_sfps_error_to_fingerprint,
+ R.raw.biometricprompt_sfps_error_to_fingerprint_90,
+ R.raw.biometricprompt_sfps_error_to_fingerprint_180,
+ R.raw.biometricprompt_sfps_error_to_fingerprint_270,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_90,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_180,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_270,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_270,
+ R.raw.biometricprompt_sfps_fingerprint_to_success,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_270,
+ )
+ } else {
+ listOf(
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie,
+ R.raw.fingerprint_dialogue_error_to_unlock_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie,
+ R.raw.fingerprint_dialogue_error_to_success_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie,
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ )
+ }
+
+ /** Fingerprint iconView assets for caching */
+ fun getFingerprintAssetsList(hasSfps: Boolean): List<Int> =
+ if (hasSfps) {
+ listOf(
+ R.raw.biometricprompt_sfps_fingerprint_authenticating,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating,
+ R.raw.biometricprompt_sfps_error_to_success,
+ R.raw.biometricprompt_sfps_fingerprint_to_error,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_error_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_270,
+ R.raw.biometricprompt_sfps_error_to_fingerprint,
+ R.raw.biometricprompt_sfps_error_to_fingerprint_90,
+ R.raw.biometricprompt_sfps_error_to_fingerprint_180,
+ R.raw.biometricprompt_sfps_error_to_fingerprint_270,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_90,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_180,
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_270,
+ R.raw.biometricprompt_sfps_fingerprint_to_success,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_90,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_180,
+ R.raw.biometricprompt_sfps_fingerprint_to_success_270,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_90,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_180,
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_270,
)
} else {
listOf(
@@ -689,5 +623,120 @@
R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
)
}
- }
+
+ /** Face iconView assets for caching */
+ fun getFaceAssetsList(): List<Int> =
+ listOf(
+ R.raw.face_dialog_wink_from_dark,
+ R.raw.face_dialog_dark_to_checkmark,
+ R.raw.face_dialog_dark_to_error,
+ R.raw.face_dialog_error_to_idle,
+ R.raw.face_dialog_idle_static,
+ R.raw.face_dialog_authenticating
+ )
+
+ private fun getSfpsAsset_fingerprintAuthenticating(isInRearDisplayMode: Boolean): Int =
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating
+ } else {
+ R.raw.biometricprompt_sfps_fingerprint_authenticating
+ }
+
+ private fun getSfpsAsset_fingerprintToError(
+ rotation: DisplayRotation,
+ isInRearDisplayMode: Boolean
+ ): Int =
+ if (isInRearDisplayMode) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_90
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_180
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error_270
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_sfps_fingerprint_to_error
+ DisplayRotation.ROTATION_90 -> R.raw.biometricprompt_sfps_fingerprint_to_error_90
+ DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_sfps_fingerprint_to_error_180
+ DisplayRotation.ROTATION_270 -> R.raw.biometricprompt_sfps_fingerprint_to_error_270
+ }
+ }
+
+ private fun getSfpsAsset_errorToFingerprint(
+ rotation: DisplayRotation,
+ isInRearDisplayMode: Boolean
+ ): Int =
+ if (isInRearDisplayMode) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_90
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_180
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint_270
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_sfps_error_to_fingerprint
+ DisplayRotation.ROTATION_90 -> R.raw.biometricprompt_sfps_error_to_fingerprint_90
+ DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_sfps_error_to_fingerprint_180
+ DisplayRotation.ROTATION_270 -> R.raw.biometricprompt_sfps_error_to_fingerprint_270
+ }
+ }
+
+ private fun getSfpsAsset_fingerprintToUnlock(
+ rotation: DisplayRotation,
+ isInRearDisplayMode: Boolean
+ ): Int =
+ if (isInRearDisplayMode) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_90
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_180
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock_270
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_sfps_fingerprint_to_unlock
+ DisplayRotation.ROTATION_90 -> R.raw.biometricprompt_sfps_fingerprint_to_unlock_90
+ DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_sfps_fingerprint_to_unlock_180
+ DisplayRotation.ROTATION_270 -> R.raw.biometricprompt_sfps_fingerprint_to_unlock_270
+ }
+ }
+
+ private fun getSfpsAsset_fingerprintToSuccess(
+ rotation: DisplayRotation,
+ isInRearDisplayMode: Boolean
+ ): Int =
+ if (isInRearDisplayMode) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_90
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_180
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success_270
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_sfps_fingerprint_to_success
+ DisplayRotation.ROTATION_90 -> R.raw.biometricprompt_sfps_fingerprint_to_success_90
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_sfps_fingerprint_to_success_180
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_sfps_fingerprint_to_success_270
+ }
+ }
}
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 874ad9e..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
@@ -409,12 +409,7 @@
.distinctUntilChanged()
val iconViewModel: PromptIconViewModel =
- PromptIconViewModel(
- this,
- displayStateInteractor,
- promptSelectorInteractor,
- udfpsOverlayInteractor
- )
+ PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
private val _isIconViewLoaded = MutableStateFlow(false)
@@ -470,26 +465,13 @@
}
}
- /** Logo for the prompt. */
- val logo: Flow<Drawable?> =
+ /** (logoIcon, logoDescription) for the prompt. */
+ val logoInfo: Flow<Pair<Drawable?, String>> =
promptSelectorInteractor.prompt
.map {
when {
- !(customBiometricPrompt() && constraintBp()) || it == null -> null
- it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
- else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
- }
- }
- .distinctUntilChanged()
-
- /** Logo description for the prompt. */
- val logoDescription: Flow<String> =
- promptSelectorInteractor.prompt
- .map {
- when {
- !(customBiometricPrompt() && constraintBp()) || it == null -> ""
- !it.logoDescription.isNullOrEmpty() -> it.logoDescription
- else -> context.getUserBadgedLabel(it, activityTaskManager)
+ !(customBiometricPrompt() && constraintBp()) || it == null -> Pair(null, "")
+ else -> context.getUserBadgedLogoInfo(it, iconProvider, activityTaskManager)
}
}
.distinctUntilChanged()
@@ -851,7 +833,7 @@
messageJob?.cancel()
messageJob = null
- if (helpMessage.isNotBlank()) {
+ if (helpMessage.isNotBlank() && needsUserConfirmation) {
showHelp(helpMessage)
}
}
@@ -916,8 +898,8 @@
): Boolean {
if (
modalities.first().hasUdfps &&
- touchExplorationEnabled &&
- !isAuthenticated.first().isAuthenticated
+ touchExplorationEnabled &&
+ !isAuthenticated.first().isAuthenticated
) {
// TODO(b/315184924): Remove uses of UdfpsUtils
val scaledTouch =
@@ -985,43 +967,60 @@
}
}
-private fun Context.getUserBadgedIcon(
+/**
+ * The order of getting logo icon/description is:
+ * 1. If the app sets customized icon/description, use the passed-in value
+ * 2. If shouldShowLogoWithOverrides(), use activityInfo to get icon/description
+ * 3. Otherwise, use applicationInfo to get icon/description
+ */
+private fun Context.getUserBadgedLogoInfo(
prompt: BiometricPromptRequest.Biometric,
iconProvider: IconProvider,
activityTaskManager: ActivityTaskManager
-): Drawable? {
- var icon: Drawable? = null
- val componentName = prompt.getComponentNameForLogo(activityTaskManager)
- if (componentName != null && shouldShowLogoWithOverrides(componentName)) {
- val activityInfo = getActivityInfo(componentName)
- icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo)
+): Pair<Drawable?, String> {
+ var icon: Drawable? =
+ if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null
+ var label = prompt.logoDescription ?: ""
+ if (icon != null && label.isNotEmpty()) {
+ return Pair(icon, label)
}
- if (icon == null) {
- val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
- if (appInfo == null) {
- Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
- return null
- } else {
- icon = packageManager.getApplicationIcon(appInfo)
+
+ // Use activityInfo if shouldUseActivityLogo() is true
+ val componentName = prompt.getComponentNameForLogo(activityTaskManager)
+ if (componentName != null && shouldUseActivityLogo(componentName)) {
+ val activityInfo = getActivityInfo(componentName)
+ if (activityInfo != null) {
+ if (icon == null) {
+ icon = iconProvider.getIcon(activityInfo)
+ }
+ if (label.isEmpty()) {
+ label = activityInfo.loadLabel(packageManager).toString()
+ }
}
}
- return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId))
-}
-
-private fun Context.getUserBadgedLabel(
- prompt: BiometricPromptRequest.Biometric,
- activityTaskManager: ActivityTaskManager
-): String {
- val componentName = prompt.getComponentNameForLogo(activityTaskManager)
- val appInfo = prompt.getApplicationInfoForLogo(this, componentName)
- return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) {
- Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
- ""
- } else {
- packageManager
- .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId))
- .toString()
+ if (icon != null && label.isNotEmpty()) {
+ return Pair(icon, label)
}
+
+ // Use applicationInfo for other cases
+ val appInfo = prompt.getApplicationInfo(this, componentName)
+ if (appInfo == null) {
+ Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName")
+ } else {
+ if (icon == null) {
+ icon = packageManager.getApplicationIcon(appInfo)
+ }
+ if (label.isEmpty()) {
+ label =
+ packageManager
+ .getUserBadgedLabel(
+ packageManager.getApplicationLabel(appInfo),
+ UserHandle.of(userId)
+ )
+ .toString()
+ }
+ }
+ return Pair(icon, label)
}
private fun BiometricPromptRequest.Biometric.getComponentNameForLogo(
@@ -1039,7 +1038,7 @@
}
}
-private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo(
+private fun BiometricPromptRequest.Biometric.getApplicationInfo(
context: Context,
componentNameForLogo: ComponentName?
): ApplicationInfo? {
@@ -1056,14 +1055,22 @@
Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName")
null
} else {
- context.getApplicationInfo(packageName)
+ try {
+ context.packageManager.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
+ null
+ }
}
}
-private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean {
- return resources
- .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides)
- .find { componentName.packageName.contentEquals(it) } != null
+private fun Context.shouldUseActivityLogo(componentName: ComponentName): Boolean {
+ return resources.getStringArray(R.array.config_useActivityLogoForBiometricPrompt).find {
+ componentName.packageName.contentEquals(it)
+ } != null
}
private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? =
@@ -1074,17 +1081,6 @@
null
}
-private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? =
- try {
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
- )
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e)
- null
- }
-
/** How the fingerprint sensor was started for the prompt. */
enum class FingerprintStartMode {
/** Fingerprint sensor has not started. */
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/common/ui/binder/IconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
index 64dedea..108e22b 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
@@ -16,7 +16,6 @@
package com.android.systemui.common.ui.binder
-import android.view.View
import android.widget.ImageView
import com.android.systemui.common.shared.model.Icon
@@ -31,13 +30,4 @@
is Icon.Resource -> view.setImageResource(icon.res)
}
}
-
- fun bindNullable(icon: Icon?, view: ImageView) {
- if (icon != null) {
- view.visibility = View.VISIBLE
- bind(icon, view)
- } else {
- view.visibility = View.GONE
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 6a20610..c780aac 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -175,7 +175,7 @@
}
}
- private fun finishCurrentTransition() {
+ private suspend fun finishCurrentTransition() {
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
1f,
@@ -285,7 +285,7 @@
currentTransitionId = internalTransitionInteractor.startTransition(transitionInfo)
}
- private fun updateProgress(progress: Float) {
+ private suspend fun updateProgress(progress: Float) {
if (currentTransitionId == null) return
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 3fc8b09..b06cf3f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -97,8 +97,10 @@
private val metricsLogger: CommunalMetricsLogger,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+ private val logger = Logger(logBuffer, "CommunalViewModel")
+
private val _isMediaHostVisible =
- conflatedCallbackFlow<Boolean> {
+ conflatedCallbackFlow {
val callback = { visible: Boolean ->
trySend(visible)
Unit
@@ -106,11 +108,18 @@
mediaHost.addVisibilityChangeListener(callback)
awaitClose { mediaHost.removeVisibilityChangeListener(callback) }
}
- .onStart { emit(mediaHost.visible) }
+ .onStart {
+ // Ensure the visibility state is correct when the hub is opened and this flow is
+ // started so that the UMO is shown when needed. The visibility state in MediaHost
+ // is not updated once its view has been detached, aka the hub is closed, which can
+ // result in this getting stuck as False and never being updated as the UMO is not
+ // shown.
+ mediaHost.updateViewVisibility()
+ emit(mediaHost.visible)
+ }
+ .onEach { logger.d({ "_isMediaHostVisible: $bool1" }) { bool1 = it } }
.flowOn(mainDispatcher)
- private val logger = Logger(logBuffer, "CommunalViewModel")
-
/** Communal content saved from the previous emission when the flow is active (not "frozen"). */
private var frozenCommunalContent: List<CommunalContentModel>? = null
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 058ca4d..10a565f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -36,7 +36,7 @@
context: Context,
private val backgroundScope: CoroutineScope,
hostId: Int,
- interactionHandler: RemoteViews.InteractionHandler,
+ private val interactionHandler: RemoteViews.InteractionHandler,
looper: Looper,
logBuffer: LogBuffer,
) : AppWidgetHost(context, hostId, interactionHandler, looper) {
@@ -55,7 +55,7 @@
appWidgetId: Int,
appWidget: AppWidgetProviderInfo?
): AppWidgetHostView {
- return CommunalAppWidgetHostView(context)
+ return CommunalAppWidgetHostView(context, interactionHandler)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 2559137..d549734 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -17,17 +17,25 @@
package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
import android.graphics.Outline
import android.graphics.Rect
import android.view.View
import android.view.ViewOutlineProvider
+import android.widget.RemoteViews
+import android.widget.RemoteViews.RemoteResponse
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
-class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
+class CommunalAppWidgetHostView(
+ context: Context,
+ private val interactionHandler: RemoteViews.InteractionHandler,
+) : AppWidgetHostView(context, interactionHandler), LaunchableView {
private val launchableViewDelegate =
LaunchableViewDelegate(
this,
@@ -92,4 +100,26 @@
launchableViewDelegate.setShouldBlockVisibilityChanges(block)
override fun setVisibility(visibility: Int) = launchableViewDelegate.setVisibility(visibility)
+
+ override fun onDefaultViewClicked(view: View) {
+ AppWidgetManager.getInstance(context)?.noteAppWidgetTapped(appWidgetId)
+ if (appWidgetInfo == null) {
+ return
+ }
+ val launcherApps = context.getSystemService(LauncherApps::class.java)
+ val activityInfo: LauncherActivityInfo =
+ launcherApps
+ .getActivityList(appWidgetInfo.provider.packageName, appWidgetInfo.profile)
+ ?.getOrNull(0) ?: return
+
+ val intent =
+ launcherApps.getMainActivityLaunchIntent(
+ activityInfo.componentName,
+ null,
+ activityInfo.user
+ )
+ if (intent != null) {
+ interactionHandler.onInteraction(view, intent, RemoteResponse.fromPendingIntent(intent))
+ }
+ }
}
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 b8019ab..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,14 +18,14 @@
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
+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
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractorImpl
-import com.android.systemui.shared.education.GestureType
import dagger.Binds
import dagger.Lazy
import dagger.Module
@@ -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 248b7a5..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.education.dagger.ContextualEducationModule.EduClock
-import com.android.systemui.education.data.model.GestureEduModel
-import com.android.systemui.shared.education.GestureType
-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 b7fc773..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,11 +25,11 @@
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.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.GestureEduModel
-import com.android.systemui.shared.education.GestureType
import java.time.Instant
import javax.inject.Inject
import javax.inject.Provider
@@ -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 3036d97..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,12 +17,15 @@
package com.android.systemui.education.domain.interactor
import com.android.systemui.CoreStartable
+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.shared.education.GestureType
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import java.time.Clock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -42,11 +45,12 @@
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 {
- val backGestureModelFlow = readEduModelsOnSignalCountChanged(GestureType.BACK_GESTURE)
+ val backGestureModelFlow = readEduModelsOnSignalCountChanged(BACK)
override fun start() {
backgroundScope.launch {
@@ -63,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 247abf1..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.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 com.android.systemui.shared.education.GestureType.BACK_GESTURE
+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_GESTURE, 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/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
index 643e571..3223433 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.education.GestureType
+import com.android.systemui.contextualeducation.GestureType
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
index 85f4012..d92fb9b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
@@ -16,7 +16,7 @@
package com.android.systemui.education.shared.model
-import com.android.systemui.shared.education.GestureType
+import com.android.systemui.contextualeducation.GestureType
/**
* Model for education triggered. [gestureType] indicates what gesture it is trying to educate about
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 6e4038d..cd0b3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -23,24 +23,25 @@
import com.android.server.notification.Flags.politeNotifications
import com.android.server.notification.Flags.vibrateWhileUnlocked
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
+import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
+import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.Flags.communalHub
+import com.android.systemui.Flags.statusBarCallChipNotificationIcon
+import com.android.systemui.Flags.statusBarScreenSharingChips
+import com.android.systemui.Flags.statusBarUseReposForCallChip
import com.android.systemui.dagger.SysUISingleton
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.NotificationsHeadsUpRefactor
-import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
@@ -55,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
@@ -75,8 +74,9 @@
// 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
}
private inline val politeNotifications
@@ -96,4 +96,17 @@
private inline val communalHub
get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
+
+ private inline val statusBarCallChipNotificationIconToken
+ get() =
+ FlagToken(
+ FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON,
+ statusBarCallChipNotificationIcon()
+ )
+
+ private inline val statusBarScreenSharingChipsToken
+ get() = FlagToken(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, statusBarScreenSharingChips())
+
+ private inline val statusBarUseReposForCallChipToken
+ get() = FlagToken(FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP, statusBarUseReposForCallChip())
}
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/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 67aedde..63f3d52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.FlowRowScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -208,9 +209,24 @@
Spacer(modifier = Modifier.height(6.dp))
ShortcutsSearchBar(onSearchQueryChanged)
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(searchQuery, categories, selectedCategoryType, onCategorySelected)
- Spacer(modifier = Modifier.weight(1f))
- KeyboardSettings(onClick = onKeyboardSettingsClicked)
+ if (categories.isEmpty()) {
+ Box(modifier = Modifier.weight(1f)) {
+ NoSearchResultsText(horizontalPadding = 16.dp, fillHeight = true)
+ }
+ } else {
+ CategoriesPanelSinglePane(
+ searchQuery,
+ categories,
+ selectedCategoryType,
+ onCategorySelected
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ KeyboardSettings(
+ horizontalPadding = 16.dp,
+ verticalPadding = 32.dp,
+ onClick = onKeyboardSettingsClicked
+ )
}
}
@@ -429,7 +445,7 @@
@Composable
private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
if (category == null) {
- // TODO(b/353953351) - Show a "no results" UI?
+ NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false)
return
}
LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
@@ -441,6 +457,24 @@
}
@Composable
+private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) {
+ var modifier = Modifier.fillMaxWidth()
+ if (fillHeight) {
+ modifier = modifier.fillMaxHeight()
+ }
+ Text(
+ stringResource(R.string.shortcut_helper_no_search_results),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier =
+ modifier
+ .padding(vertical = 8.dp)
+ .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp))
+ .padding(horizontal = horizontalPadding, vertical = 24.dp)
+ )
+}
+
+@Composable
private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: ShortcutSubCategory) {
Surface(
modifier = Modifier.fillMaxWidth(),
@@ -659,7 +693,11 @@
Spacer(modifier = Modifier.heightIn(8.dp))
CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
Spacer(modifier = Modifier.weight(1f))
- KeyboardSettings(onKeyboardSettingsClicked)
+ KeyboardSettings(
+ horizontalPadding = 24.dp,
+ verticalPadding = 24.dp,
+ onKeyboardSettingsClicked
+ )
}
}
@@ -805,10 +843,9 @@
}
@Composable
-private fun KeyboardSettings(onClick: () -> Unit) {
+private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
-
Surface(
onClick = onClick,
shape = RoundedCornerShape(24.dp),
@@ -834,7 +871,7 @@
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 16.sp
)
- Spacer(modifier = Modifier.width(8.dp))
+ Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = Icons.AutoMirrored.Default.OpenInNew,
contentDescription = null,
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/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index aaeeb39..de60c11 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -105,7 +105,7 @@
* When the transition is over, TransitionState.FINISHED must be passed into the [state]
* parameter.
*/
- fun updateTransition(
+ suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState
@@ -173,9 +173,9 @@
Log.d(TAG, "(Internal) Setting current transition info: $info")
// There is no fairness guarantee with 'withContext', which means that transitions could
- // be processed out of order. Use a Mutex to guarantee ordering.
+ // be processed out of order. Use a Mutex to guarantee ordering. [updateTransition]
+ // requires the same lock
_currentTransitionMutex.lock()
-
// Only used in a test environment
if (forceDelayForRaceConditionTest) {
delay(50L)
@@ -184,7 +184,6 @@
// Animators must be started on the main thread.
return withContext("$TAG#startTransition", mainDispatcher) {
_currentTransitionMutex.unlock()
-
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return@withContext null
@@ -206,7 +205,7 @@
// Cancel any existing manual transitions
updateTransitionId?.let { uuid ->
- updateTransition(uuid, lastStep.value, TransitionState.CANCELED)
+ updateTransitionInternal(uuid, lastStep.value, TransitionState.CANCELED)
}
info.animator?.let { animator ->
@@ -264,7 +263,23 @@
}
}
- override fun updateTransition(
+ override suspend fun updateTransition(
+ transitionId: UUID,
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ state: TransitionState
+ ) {
+ // There is no fairness guarantee with 'withContext', which means that transitions could
+ // be processed out of order. Use a Mutex to guarantee ordering. [startTransition]
+ // requires the same lock
+ _currentTransitionMutex.lock()
+ withContext("$TAG#updateTransition", mainDispatcher) {
+ _currentTransitionMutex.unlock()
+
+ updateTransitionInternal(transitionId, value, state)
+ }
+ }
+
+ private suspend fun updateTransitionInternal(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState
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/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 91ee287..befcc9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -96,6 +96,8 @@
when (toState) {
KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+ KeyguardState.ALTERNATE_BOUNCER -> TO_BOUNCER_DURATION
+ KeyguardState.PRIMARY_BOUNCER -> TO_BOUNCER_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -269,8 +271,16 @@
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
- val DEFAULT_DURATION = 1.seconds
- val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
+
+ /**
+ * DEFAULT_DURATION controls the timing for all animations other than those with overrides
+ * in [getDefaultAnimatorForTransitionsToState].
+ *
+ * Set at 400ms for parity with [FromLockscreenTransitionInteractor]
+ */
+ val DEFAULT_DURATION = 400.milliseconds
+ val TO_LOCKSCREEN_DURATION = 1.seconds
+ val TO_BOUNCER_DURATION = 400.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}
}
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/InternalKeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
index a51421c..2cc6afa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/InternalKeyguardTransitionInteractor.kt
@@ -63,7 +63,7 @@
suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
- fun updateTransition(
+ suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState
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 797d466..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
@@ -47,6 +47,7 @@
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -55,6 +56,7 @@
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.launch
/** Encapsulates business-logic related to the keyguard transitions. */
@@ -73,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>>()
@@ -150,6 +153,12 @@
startedStep.to != prevStep.from
) {
getTransitionValueFlow(prevStep.from).emit(0f)
+ } else if (prevStep.transitionState == TransitionState.RUNNING) {
+ Log.e(
+ TAG,
+ "STARTED step ($startedStep) was preceded by a RUNNING step " +
+ "($prevStep), which should never happen. Things could go badly here."
+ )
}
}
}
@@ -252,15 +261,12 @@
val startedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
- /** The last [TransitionStep] with a [TransitionState] of FINISHED */
- val finishedKeyguardTransitionStep: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
-
/** The destination state of the last [TransitionState.STARTED] transition. */
@SuppressLint("SharedFlowCreation")
val startedKeyguardState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
+ .buffer(2, BufferOverflow.DROP_OLDEST)
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
/** The from state of the last [TransitionState.STARTED] transition. */
@@ -269,6 +275,7 @@
val startedKeyguardFromState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.from }
+ .buffer(2, BufferOverflow.DROP_OLDEST)
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
/** Which keyguard state to use when the device goes to sleep. */
@@ -310,8 +317,13 @@
*/
@SuppressLint("SharedFlowCreation")
val finishedKeyguardState: SharedFlow<KeyguardState> =
- finishedKeyguardTransitionStep
- .map { step -> step.to }
+ repository.transitions
+ .transform { step ->
+ if (step.transitionState == TransitionState.FINISHED) {
+ emit(step.to)
+ }
+ }
+ .buffer(2, BufferOverflow.DROP_OLDEST)
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
/**
@@ -407,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/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
index 3248114..b850095 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -124,7 +124,7 @@
}
}
- private fun finishCurrentTransition() {
+ private suspend fun finishCurrentTransition() {
internalTransitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
resetTransitionData()
}
@@ -223,7 +223,7 @@
currentTransitionId = internalTransitionInteractor.startTransition(transitionInfo)
}
- private fun updateProgress(progress: Float) {
+ private suspend fun updateProgress(progress: Float) {
if (currentTransitionId == null) return
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
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 f96f053..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
@@ -207,14 +206,12 @@
launch {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
- childViews[aodNotificationIconContainerId]?.visibility = visibility
}
}
launch {
viewModel.burnInLayerAlpha.collect { alpha ->
childViews[statusViewId]?.alpha = alpha
- childViews[aodNotificationIconContainerId]?.alpha = alpha
}
}
@@ -294,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,
+ )
}
}
@@ -521,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 11889c5..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),
@@ -320,19 +322,24 @@
val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> =
combine(
goneToAodTransitionRunning,
+ keyguardTransitionInteractor
+ .transitionValue(LOCKSCREEN)
+ .map { it > 0f }
+ .onStart { emit(false) },
keyguardTransitionInteractor.finishedKeyguardState.map {
KeyguardState.lockscreenVisibleInState(it)
},
deviceEntryInteractor.isBypassEnabled,
areNotifsFullyHiddenAnimated(),
isPulseExpandingAnimated(),
- ) {
- goneToAodTransitionRunning: Boolean,
- onKeyguard: Boolean,
- isBypassEnabled: Boolean,
- notifsFullyHidden: AnimatedValue<Boolean>,
- pulseExpanding: AnimatedValue<Boolean>,
- ->
+ ) { flows ->
+ val goneToAodTransitionRunning = flows[0] as Boolean
+ val isOnLockscreen = flows[1] as Boolean
+ val onKeyguard = flows[2] as Boolean
+ val isBypassEnabled = flows[3] as Boolean
+ val notifsFullyHidden = flows[4] as AnimatedValue<Boolean>
+ val pulseExpanding = flows[5] as AnimatedValue<Boolean>
+
when {
// Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
// animation is playing, in which case we want them to be visible if we're
@@ -351,6 +358,8 @@
isBypassEnabled -> true
// If we are pulsing (and not bypassing), then we are hidden
isPulseExpanding -> false
+ // Besides bypass above, they should not be visible on lockscreen
+ isOnLockscreen -> false
// If notifs are fully gone, then we're visible
areNotifsFullyHidden -> true
// Otherwise, we're hidden
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/activatable/Activatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
rename to packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
index dc2d931..ebb0ea62 100644
--- a/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.activatable
+package com.android.systemui.lifecycle
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
/** Defines interface for classes that can be activated to do coroutine work. */
interface Activatable {
@@ -55,3 +59,20 @@
*/
suspend fun activate()
}
+
+/**
+ * Returns a remembered [Activatable] of the type [T] that's automatically kept active until this
+ * composable leaves the composition.
+ *
+ * If the [key] changes, the old [Activatable] is deactivated and a new one will be instantiated,
+ * activated, and returned.
+ */
+@Composable
+fun <T : Activatable> rememberActivated(
+ key: Any = Unit,
+ factory: () -> T,
+): T {
+ val instance = remember(key) { factory() }
+ LaunchedEffect(instance) { instance.activate() }
+ return instance
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt
new file mode 100644
index 0000000..f080a42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.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.
+ */
+
+package com.android.systemui.lifecycle
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * An [Activatable] that can be concurrently activated by no more than one owner.
+ *
+ * A previous call to [activate] must be canceled before a new call to [activate] can be made.
+ * Trying to call [activate] while already active will fail with an error.
+ */
+abstract class SafeActivatable : Activatable {
+
+ private val _isActive = AtomicBoolean(false)
+
+ var isActive: Boolean
+ get() = _isActive.get()
+ private set(value) {
+ _isActive.set(value)
+ }
+
+ final override suspend fun activate() {
+ val allowed = _isActive.compareAndSet(false, true)
+ check(allowed) { "Cannot activate an already active activatable!" }
+
+ try {
+ onActivated()
+ } finally {
+ isActive = false
+ }
+ }
+
+ /**
+ * Notifies that the [Activatable] has been activated.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+ * its state fresh and/or perform side-effects.
+ *
+ * The method suspends and doesn't return until all work required by the object is finished. In
+ * most cases, it's expected for the work to remain ongoing forever so this method will forever
+ * suspend its caller until the coroutine that called it is canceled.
+ *
+ * Implementations could follow this pattern:
+ * ```kotlin
+ * override suspend fun onActivated() {
+ * coroutineScope {
+ * launch { ... }
+ * launch { ... }
+ * launch { ... }
+ * }
+ * }
+ * ```
+ *
+ * @see activate
+ */
+ protected abstract suspend fun onActivated()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
new file mode 100644
index 0000000..0af5fea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.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.
+ */
+
+package com.android.systemui.lifecycle
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+
+/** Base class for all System UI view-models. */
+abstract class SysUiViewModel : SafeActivatable() {
+
+ override suspend fun onActivated() = Unit
+}
+
+/**
+ * Returns a remembered [SysUiViewModel] of the type [T] that's automatically kept active until this
+ * composable leaves the composition.
+ *
+ * If the [key] changes, the old [SysUiViewModel] is deactivated and a new one will be instantiated,
+ * activated, and returned.
+ */
+@Composable
+fun <T : SysUiViewModel> rememberViewModel(
+ key: Any = Unit,
+ factory: () -> T,
+): T {
+ val instance = remember(key) { factory() }
+ LaunchedEffect(instance) { instance.activate() }
+ return instance
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt
new file mode 100644
index 0000000..723ff5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.mediaprojection
+
+import android.content.pm.PackageManager
+import com.android.systemui.util.Utils
+
+/** Various utility methods related to media projection. */
+object MediaProjectionUtils {
+ /**
+ * Returns true iff projecting to the given [packageName] means that we're casting media to a
+ * *different* device (as opposed to sharing media to some application on *this* device).
+ */
+ fun packageHasCastingCapabilities(
+ packageManager: PackageManager,
+ packageName: String
+ ): Boolean {
+ // The [isHeadlessRemoteDisplayProvider] check approximates whether a projection is to a
+ // different device or the same device, because headless remote display packages are the
+ // only kinds of packages that do cast-to-other-device. This isn't exactly perfect,
+ // because it means that any projection by those headless remote display packages will be
+ // marked as going to a different device, even if that isn't always true. See b/321078669.
+ return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index 83f694b..cdf8f06 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -95,13 +95,16 @@
private fun initScreenShareOptions() {
selectedScreenShareOption = screenShareOptions.first { it.mode == defaultSelectedMode }
- warning.text = warningText
+ setOptionSpecificFields()
initScreenShareSpinner()
}
private val warningText: String
get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
+ private val startButtonText: String
+ get() = dialog.context.getString(selectedScreenShareOption.startButtonText)
+
private fun initScreenShareSpinner() {
val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_options)
@@ -126,7 +129,13 @@
override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
selectedScreenShareOption = screenShareOptions[pos]
+ setOptionSpecificFields()
+ }
+
+ /** Sets fields on the dialog that change based on which option is selected. */
+ private fun setOptionSpecificFields() {
warning.text = warningText
+ startButton.text = startButtonText
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
@@ -137,10 +146,6 @@
dialogTitle.text = title
}
- protected fun setStartButtonText(@StringRes stringId: Int) {
- startButton.setText(stringId)
- }
-
protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
startButton.setOnClickListener { view ->
shouldLogCancel = false
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index cc4a92c..3c83db3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -41,7 +41,6 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.graphics.Typeface;
import android.media.projection.IMediaProjection;
import android.media.projection.MediaProjectionConfig;
import android.media.projection.MediaProjectionManager;
@@ -50,10 +49,8 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.BidiFormatter;
-import android.text.SpannableString;
import android.text.TextPaint;
import android.text.TextUtils;
-import android.text.style.StyleSpan;
import android.util.Log;
import android.view.Window;
@@ -61,6 +58,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.MediaProjectionUtils;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
@@ -68,12 +66,13 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.util.Utils;
-import dagger.Lazy;
+import java.util.function.Consumer;
import javax.inject.Inject;
+import dagger.Lazy;
+
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener {
private static final String TAG = "MediaProjectionPermissionActivity";
@@ -189,30 +188,14 @@
final String appName = extractAppName(aInfo, packageManager);
final boolean hasCastingCapabilities =
- Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
+ MediaProjectionUtils.INSTANCE.packageHasCastingCapabilities(
+ packageManager, mPackageName);
// Using application context for the dialog, instead of the activity context, so we get
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
- MediaProjectionPermissionDialogDelegate delegate =
- new MediaProjectionPermissionDialogDelegate(
- dialogContext,
- getMediaProjectionConfig(),
- dialog -> {
- ScreenShareOption selectedOption =
- dialog.getSelectedScreenShareOption();
- grantMediaProjectionPermission(selectedOption.getMode());
- },
- () -> finish(RECORD_CANCEL, /* projection= */ null),
- hasCastingCapabilities,
- appName,
- overrideDisableSingleAppOption,
- mUid,
- mMediaProjectionMetricsLogger);
+ BaseMediaProjectionPermissionDialogDelegate<AlertDialog> delegate =
+ createPermissionDialogDelegate(appName, hasCastingCapabilities, dialogContext);
mDialog =
new AlertDialogWithDelegate(
dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
@@ -274,6 +257,44 @@
return appName;
}
+ private BaseMediaProjectionPermissionDialogDelegate<AlertDialog> createPermissionDialogDelegate(
+ String appName,
+ boolean hasCastingCapabilities,
+ Context dialogContext) {
+ final boolean overrideDisableSingleAppOption =
+ CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
+ MediaProjectionConfig mediaProjectionConfig = getMediaProjectionConfig();
+ Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>> onStartRecordingClicked =
+ dialog -> {
+ ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ };
+ Runnable onCancelClicked = () -> finish(RECORD_CANCEL, /* projection= */ null);
+ if (hasCastingCapabilities) {
+ return new SystemCastPermissionDialogDelegate(
+ dialogContext,
+ mediaProjectionConfig,
+ onStartRecordingClicked,
+ onCancelClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ } else {
+ return new ShareToAppPermissionDialogDelegate(
+ dialogContext,
+ mediaProjectionConfig,
+ onStartRecordingClicked,
+ onCancelClicked,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ }
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
deleted file mode 100644
index 6d1a458..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.mediaprojection.permission
-
-import android.app.AlertDialog
-import android.content.Context
-import android.media.projection.MediaProjectionConfig
-import android.os.Bundle
-import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
-import com.android.systemui.res.R
-import java.util.function.Consumer
-
-/** Dialog to select screen recording options */
-class MediaProjectionPermissionDialogDelegate(
- context: Context,
- mediaProjectionConfig: MediaProjectionConfig?,
- private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
- private val onCancelClicked: Runnable,
- private val hasCastingCapabilities: Boolean,
- appName: String,
- forceShowPartialScreenshare: Boolean,
- hostUid: Int,
- mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
-) :
- BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(
- context,
- appName,
- hasCastingCapabilities,
- mediaProjectionConfig,
- forceShowPartialScreenshare
- ),
- appName,
- hostUid,
- mediaProjectionMetricsLogger
- ) {
- override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
- super.onCreate(dialog, savedInstanceState)
- // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
- if (hasCastingCapabilities) {
- setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
- setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue)
- } else {
- setDialogTitle(R.string.media_projection_entry_app_permission_dialog_title)
- setStartButtonText(R.string.media_projection_entry_app_permission_dialog_continue)
- }
- setStartButtonOnClickListener {
- // Note that it is important to run this callback before dismissing, so that the
- // callback can disable the dialog exit animation if it wants to.
- onStartRecordingClicked.accept(this)
- dialog.dismiss()
- }
- setCancelButtonOnClickListener {
- onCancelClicked.run()
- dialog.dismiss()
- }
- }
-
- companion object {
- private fun createOptionList(
- context: Context,
- appName: String,
- hasCastingCapabilities: Boolean,
- mediaProjectionConfig: MediaProjectionConfig?,
- overrideDisableSingleAppOption: Boolean = false,
- ): List<ScreenShareOption> {
- val singleAppWarningText =
- if (hasCastingCapabilities) {
- R.string.media_projection_entry_cast_permission_dialog_warning_single_app
- } else {
- R.string.media_projection_entry_app_permission_dialog_warning_single_app
- }
- val entireScreenWarningText =
- if (hasCastingCapabilities) {
- R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen
- } else {
- R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
- }
-
- // The single app option should only be disabled if the client has setup a
- // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
- // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
- val singleAppOptionDisabled =
- !overrideDisableSingleAppOption &&
- mediaProjectionConfig?.regionToCapture ==
- MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
-
- val singleAppDisabledText =
- if (singleAppOptionDisabled) {
- context.getString(
- R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
- appName
- )
- } else {
- null
- }
- val options =
- listOf(
- ScreenShareOption(
- mode = SINGLE_APP,
- spinnerText = R.string.screen_share_permission_dialog_option_single_app,
- warningText = singleAppWarningText,
- spinnerDisabledText = singleAppDisabledText,
- ),
- ScreenShareOption(
- mode = ENTIRE_SCREEN,
- spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
- warningText = entireScreenWarningText
- )
- )
- return if (singleAppOptionDisabled) {
- // Make sure "Entire screen" is the first option when "Single App" is disabled.
- options.reversed()
- } else {
- options
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
new file mode 100644
index 0000000..88cbc38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.mediaprojection.permission
+
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import com.android.systemui.res.R
+
+/** Various utility methods related to media projection permissions. */
+object MediaProjectionPermissionUtils {
+ fun getSingleAppDisabledText(
+ context: Context,
+ appName: String,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean,
+ ): String? {
+ // The single app option should only be disabled if the client has setup a
+ // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
+ // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
+ val singleAppOptionDisabled =
+ !overrideDisableSingleAppOption &&
+ mediaProjectionConfig?.regionToCapture ==
+ MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+ return if (singleAppOptionDisabled) {
+ context.getString(
+ R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
+ appName,
+ )
+ } else {
+ null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
index 9bd5783..ab92173 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt
@@ -26,9 +26,10 @@
const val SINGLE_APP = 0
const val ENTIRE_SCREEN = 1
-class ScreenShareOption(
+data class ScreenShareOption(
@ScreenShareMode val mode: Int,
@StringRes val spinnerText: Int,
@StringRes val warningText: Int,
+ @StringRes val startButtonText: Int,
val spinnerDisabledText: String? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
new file mode 100644
index 0000000..4b132d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import java.util.function.Consumer
+
+/**
+ * Dialog to select screen recording options for sharing the screen to another app on the same
+ * device.
+ */
+class ShareToAppPermissionDialogDelegate(
+ context: Context,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ private val onStartRecordingClicked:
+ Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>>,
+ private val onCancelClicked: Runnable,
+ appName: String,
+ forceShowPartialScreenshare: Boolean,
+ hostUid: Int,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+) :
+ BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
+ createOptionList(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption = forceShowPartialScreenshare,
+ ),
+ appName,
+ hostUid,
+ mediaProjectionMetricsLogger,
+ dialogIconDrawable = R.drawable.ic_present_to_all,
+ ) {
+ override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+ super.onCreate(dialog, savedInstanceState)
+ // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
+ setDialogTitle(R.string.media_projection_entry_app_permission_dialog_title)
+ setStartButtonOnClickListener {
+ // Note that it is important to run this callback before dismissing, so that the
+ // callback can disable the dialog exit animation if it wants to.
+ onStartRecordingClicked.accept(this)
+ dialog.dismiss()
+ }
+ setCancelButtonOnClickListener {
+ onCancelClicked.run()
+ dialog.dismiss()
+ }
+ }
+
+ companion object {
+ private fun createOptionList(
+ context: Context,
+ appName: String,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean,
+ ): List<ScreenShareOption> {
+ val singleAppDisabledText =
+ MediaProjectionPermissionUtils.getSingleAppDisabledText(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption,
+ )
+ val options =
+ listOf(
+ ScreenShareOption(
+ mode = SINGLE_APP,
+ spinnerText =
+ R.string
+ .media_projection_entry_app_permission_dialog_option_text_single_app,
+ warningText =
+ R.string
+ .media_projection_entry_app_permission_dialog_warning_single_app,
+ startButtonText =
+ R.string
+ .media_projection_entry_generic_permission_dialog_continue_single_app,
+ spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText =
+ R.string
+ .media_projection_entry_app_permission_dialog_option_text_entire_screen,
+ warningText =
+ R.string
+ .media_projection_entry_app_permission_dialog_warning_entire_screen,
+ startButtonText =
+ R.string
+ .media_projection_entry_app_permission_dialog_continue_entire_screen,
+ )
+ )
+ return if (singleAppDisabledText != null) {
+ // Make sure "Entire screen" is the first option when "Single App" is disabled.
+ options.reversed()
+ } else {
+ options
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt
new file mode 100644
index 0000000..a19fb96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegate.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.content.Context
+import android.media.projection.MediaProjectionConfig
+import android.os.Bundle
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionUtils.getSingleAppDisabledText
+import com.android.systemui.res.R
+import java.util.function.Consumer
+
+/** Dialog to select screen recording options for casting the screen to a different device. */
+class SystemCastPermissionDialogDelegate(
+ context: Context,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ private val onStartRecordingClicked:
+ Consumer<BaseMediaProjectionPermissionDialogDelegate<AlertDialog>>,
+ private val onCancelClicked: Runnable,
+ appName: String,
+ forceShowPartialScreenshare: Boolean,
+ hostUid: Int,
+ mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+) :
+ BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
+ createOptionList(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption = forceShowPartialScreenshare,
+ ),
+ appName,
+ hostUid,
+ mediaProjectionMetricsLogger,
+ dialogIconDrawable = R.drawable.ic_cast_connected,
+ ) {
+ override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) {
+ super.onCreate(dialog, savedInstanceState)
+ // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
+ setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title)
+ setStartButtonOnClickListener {
+ // Note that it is important to run this callback before dismissing, so that the
+ // callback can disable the dialog exit animation if it wants to.
+ onStartRecordingClicked.accept(this)
+ dialog.dismiss()
+ }
+ setCancelButtonOnClickListener {
+ onCancelClicked.run()
+ dialog.dismiss()
+ }
+ }
+
+ companion object {
+ private fun createOptionList(
+ context: Context,
+ appName: String,
+ mediaProjectionConfig: MediaProjectionConfig?,
+ overrideDisableSingleAppOption: Boolean,
+ ): List<ScreenShareOption> {
+ val singleAppDisabledText =
+ getSingleAppDisabledText(
+ context,
+ appName,
+ mediaProjectionConfig,
+ overrideDisableSingleAppOption
+ )
+ val options =
+ listOf(
+ ScreenShareOption(
+ mode = SINGLE_APP,
+ spinnerText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_option_text_single_app,
+ warningText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_warning_single_app,
+ startButtonText =
+ R.string
+ .media_projection_entry_generic_permission_dialog_continue_single_app,
+ spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_option_text_entire_screen,
+ warningText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_warning_entire_screen,
+ startButtonText =
+ R.string
+ .media_projection_entry_cast_permission_dialog_continue_entire_screen,
+ )
+ )
+ return if (singleAppDisabledText != null) {
+ // Make sure "Entire screen" is the first option when "Single App" is disabled.
+ options.reversed()
+ } else {
+ options
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9eca34f..0fe4d36 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -74,6 +74,7 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
@@ -1057,6 +1058,8 @@
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
mEdgeBackPlugin.onMotionEvent(ev);
dispatchToBackAnimation(ev);
+ mOverviewProxyService.updateContextualEduStats(mIsTrackpadThreeFingerSwipe,
+ GestureType.BACK);
}
if (mLogGesture || mIsTrackpadThreeFingerSwipe) {
mDownPoint.set(ev.getX(), ev.getY());
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/power/InattentiveSleepWarningView.java b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
index 1cd5d91..2ecca2d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
+++ b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
@@ -16,6 +16,8 @@
package com.android.systemui.power;
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
@@ -29,21 +31,27 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.res.R;
+import kotlin.Lazy;
+
/**
* View that shows a warning shortly before the device goes into sleep
* after prolonged user inactivity when bound to.
*/
public class InattentiveSleepWarningView extends FrameLayout {
private final IBinder mWindowToken = new Binder();
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private Animator mFadeOutAnimator;
private boolean mDismissing;
- InattentiveSleepWarningView(Context context) {
+ InattentiveSleepWarningView(Context context, Lazy<ViewCapture> lazyViewCapture) {
super(context);
- mWindowManager = mContext.getSystemService(WindowManager.class);
+ WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = new ViewCaptureAwareWindowManager(wm, lazyViewCapture,
+ enableViewCaptureTracing());
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
layoutInflater.inflate(R.layout.inattentive_sleep_warning, this, true /* attachToRoot */);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace35..861a7ce 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -16,6 +16,8 @@
package com.android.systemui.power;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -44,6 +46,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.app.viewcapture.ViewCapture;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
@@ -56,6 +59,8 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import kotlin.Lazy;
+
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.concurrent.Future;
@@ -117,6 +122,7 @@
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
+ private final Lazy<ViewCapture> mLazyViewCapture;
@Nullable
private final IVrManager mVrManager;
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
@@ -157,7 +163,8 @@
EnhancedEstimates enhancedEstimates,
WakefulnessLifecycle wakefulnessLifecycle,
PowerManager powerManager,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ dagger.Lazy<ViewCapture> daggerLazyViewCapture) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
@@ -167,6 +174,7 @@
mPowerManager = powerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUserTracker = userTracker;
+ mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
}
public void start() {
@@ -641,7 +649,7 @@
@Override
public void showInattentiveSleepWarning() {
if (mOverlayView == null) {
- mOverlayView = new InattentiveSleepWarningView(mContext);
+ mOverlayView = new InattentiveSleepWarningView(mContext, mLazyViewCapture);
}
mOverlayView.show();
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 c6f9ae8..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,15 +34,14 @@
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
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
@@ -87,8 +86,9 @@
}
}
}
- // Warm up tile with some initial state
- launch { qsTileViewModel.state.first() }
+ // Warm up tile with some initial state. Because `state` is a StateFlow with initial
+ // state `null`, we collect until it's not null.
+ launch { qsTileViewModel.state.takeWhile { it == null }.collect {} }
}
// QSTileHost doesn't call this when userId is initialized
@@ -157,17 +157,22 @@
override fun isTileReady(): Boolean = qsTileViewModel.currentState != null
+ private var cachedState = QSTile.AdapterState()
+
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
if (listening) {
- listeningClients.add(client)
- if (listeningClients.size == 1) {
+ val clientWasNotAlreadyListening = listeningClients.add(client)
+ if (clientWasNotAlreadyListening && listeningClients.size == 1) {
stateJob =
qsTileViewModel.state
.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)
}
@@ -234,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)
@@ -242,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/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 371707d..15366d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -89,6 +89,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.contextualeducation.GestureType;
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -160,6 +162,8 @@
private final NotificationShadeWindowController mStatusBarWinController;
private final Provider<SceneInteractor> mSceneInteractor;
+ private final KeyboardTouchpadEduStatsInteractor mKeyboardTouchpadEduStatsInteractor;
+
private final Runnable mConnectionRunnable = () ->
internalConnectToCurrentUser("runnable: startConnectionToCurrentUser");
private final ComponentName mRecentsComponentName;
@@ -661,7 +665,8 @@
AssistUtils assistUtils,
DumpManager dumpManager,
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
- BroadcastDispatcher broadcastDispatcher
+ BroadcastDispatcher broadcastDispatcher,
+ KeyboardTouchpadEduStatsInteractor keyboardTouchpadEduStatsInteractor
) {
// b/241601880: This component should only be running for primary users or
// secondaryUsers when visibleBackgroundUsers are supported.
@@ -698,6 +703,7 @@
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
mBroadcastDispatcher = broadcastDispatcher;
+ mKeyboardTouchpadEduStatsInteractor = keyboardTouchpadEduStatsInteractor;
if (!KeyguardWmStateRefactor.isEnabled()) {
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
@@ -929,6 +935,19 @@
return isEnabled() && !QuickStepContract.isLegacyMode(mNavBarMode);
}
+ /**
+ * Updates contextual education stats when a gesture is triggered
+ * @param isTrackpadGesture indicates if the gesture is triggered by trackpad
+ * @param gestureType type of gesture triggered
+ */
+ public void updateContextualEduStats(boolean isTrackpadGesture, GestureType gestureType) {
+ if (isTrackpadGesture) {
+ mKeyboardTouchpadEduStatsInteractor.updateShortcutTriggerTime(gestureType);
+ } else {
+ mKeyboardTouchpadEduStatsInteractor.incrementSignalCount(gestureType);
+ }
+ }
+
public boolean isEnabled() {
return mIsEnabled;
}
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/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index c7190c3..103b4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -19,7 +19,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.activatable.Activatable
+import com.android.systemui.lifecycle.Activatable
import kotlinx.coroutines.flow.Flow
/**
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/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index ab6067c..b54bf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -125,7 +125,6 @@
super<BaseMediaProjectionPermissionDialogDelegate>.onCreate(dialog, savedInstanceState)
setDialogTitle(R.string.screenrecord_permission_dialog_title)
dialog.setTitle(R.string.screenrecord_title)
- setStartButtonText(R.string.screenrecord_permission_dialog_continue)
setStartButtonOnClickListener { v: View? ->
onStartRecordingClicked?.run()
if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
@@ -272,12 +271,14 @@
ScreenShareOption(
SINGLE_APP,
R.string.screen_share_permission_dialog_option_single_app,
- R.string.screenrecord_permission_dialog_warning_single_app
+ R.string.screenrecord_permission_dialog_warning_single_app,
+ startButtonText = R.string.screenrecord_permission_dialog_continue,
),
ScreenShareOption(
ENTIRE_SCREEN,
R.string.screen_share_permission_dialog_option_entire_screen,
- R.string.screenrecord_permission_dialog_warning_entire_screen
+ R.string.screenrecord_permission_dialog_warning_entire_screen,
+ startButtonText = R.string.screenrecord_permission_dialog_continue,
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
index bc8642c..a77375c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -19,7 +19,6 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
-import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
import static com.android.systemui.Flags.screenshotSaveImageExporter;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
@@ -355,19 +354,9 @@
void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
- if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
- mAnnouncementResolver.getScreenshotAnnouncement(
- screenshot.getUserHandle().getIdentifier(),
- mViewProxy::announceForAccessibility);
- } else {
- if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
- mViewProxy.announceForAccessibility(mContext.getResources().getString(
- R.string.screenshot_saving_work_profile_title));
- } else {
- mViewProxy.announceForAccessibility(
- mContext.getResources().getString(R.string.screenshot_saving_title));
- }
- }
+ mAnnouncementResolver.getScreenshotAnnouncement(
+ screenshot.getUserHandle().getIdentifier(),
+ mViewProxy::announceForAccessibility);
});
mViewProxy.reset();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ec529cd..7b802a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -17,9 +17,7 @@
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.screenshotPrivateProfileAccessibilityAnnouncementFix;
import static com.android.systemui.Flags.screenshotSaveImageExporter;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
@@ -32,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;
@@ -53,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;
@@ -116,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
@@ -136,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 = "";
@@ -156,7 +144,7 @@
@AssistedInject
ScreenshotController(
Context context,
- WindowManager windowManager,
+ ScreenshotWindow.Factory screenshotWindowFactory,
FeatureFlags flags,
ScreenshotShelfViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
@@ -196,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;
@@ -214,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;
@@ -319,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) {
@@ -348,26 +328,15 @@
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(() -> {
- if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
- mAnnouncementResolver.getScreenshotAnnouncement(
- screenshot.getUserHandle().getIdentifier(),
- mViewProxy::announceForAccessibility);
- } else {
- if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
- mViewProxy.announceForAccessibility(mContext.getResources().getString(
- R.string.screenshot_saving_work_profile_title));
- } else {
- mViewProxy.announceForAccessibility(
- mContext.getResources().getString(R.string.screenshot_saving_title));
- }
- }
+ mWindow.whenWindowAttached(() -> {
+ mAnnouncementResolver.getScreenshotAnnouncement(
+ screenshot.getUserHandle().getIdentifier(),
+ announcement -> {
+ mViewProxy.announceForAccessibility(announcement);
+ });
});
mViewProxy.reset();
@@ -453,7 +422,7 @@
@Override
public void onTouchOutside() {
// TODO(159460485): Remove this when focus is handled properly in the system
- setWindowFocusable(false);
+ mWindow.setFocusable(false);
}
});
@@ -466,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,
@@ -481,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
@@ -498,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());
@@ -537,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();
}
@@ -768,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);
@@ -835,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/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 134c983..d1a0a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -38,4 +39,7 @@
* that is not considered "closing".
*/
abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
+
+ /** Whether a short animation to expand or collapse is running after user input has ended. */
+ abstract val isAnyFlingAnimationRunning: Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index f364d6d..dbc1b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
@SysUISingleton
@@ -29,4 +30,5 @@
shadeAnimationRepository: ShadeAnimationRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
override val isAnyCloseAnimationRunning = MutableStateFlow(false)
+ override val isAnyFlingAnimationRunning = flowOf(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
index c4f4134..32d8659 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import com.android.systemui.shade.data.repository.ShadeRepository
import javax.inject.Inject
+import kotlinx.coroutines.flow.map
/** Implementation of ShadeAnimationInteractor compatible with NPVC. */
@SysUISingleton
@@ -30,4 +31,5 @@
shadeRepository: ShadeRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
override val isAnyCloseAnimationRunning = shadeRepository.legacyIsClosing
+ override val isAnyFlingAnimationRunning = shadeRepository.currentFling.map { it != null }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index d9982e3..79a94a5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -36,12 +36,12 @@
@SysUISingleton
class ShadeAnimationInteractorSceneContainerImpl
@Inject
+@OptIn(ExperimentalCoroutinesApi::class)
constructor(
@Background scope: CoroutineScope,
shadeAnimationRepository: ShadeAnimationRepository,
sceneInteractor: SceneInteractor,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
- @OptIn(ExperimentalCoroutinesApi::class)
override val isAnyCloseAnimationRunning =
sceneInteractor.transitionState
.flatMapLatest { state ->
@@ -62,4 +62,26 @@
}
.distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isAnyFlingAnimationRunning =
+ sceneInteractor.transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> flowOf(false)
+ is ObservableTransitionState.Transition ->
+ if (
+ state.isInitiatedByUserInput &&
+ (state.fromScene == Scenes.Shade ||
+ state.toScene == Scenes.Shade ||
+ state.fromScene == Scenes.QuickSettings ||
+ state.toScene == Scenes.QuickSettings)
+ ) {
+ state.isUserInputOngoing.map { !it }
+ } else {
+ flowOf(false)
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
}
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 f90dd3c..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ /dev/null
@@ -1,155 +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.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.activatable.Activatable
-import com.android.systemui.dagger.SysUISingleton
-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.ExperimentalCoroutinesApi
-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/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index bb26f92..7244f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar;
import static android.app.Flags.keyguardPrivateNotifications;
-import static android.app.Flags.redactSensitiveContentNotificationsOnLockscreen;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
@@ -72,6 +71,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ListenerSet;
@@ -655,7 +655,7 @@
!userAllowsPrivateNotificationsInPublic(mCurrentUserId);
boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId);
boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId);
- boolean isNotifSensitive = redactSensitiveContentNotificationsOnLockscreen()
+ boolean isNotifSensitive = LockscreenOtpRedaction.isEnabled()
&& ent.getRanking() != null && ent.getRanking().hasSensitiveContent();
// redact notifications if the current user is redacting notifications or the notification
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/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 59fd0ca..18ea0b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -18,6 +18,7 @@
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.Flags
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -59,12 +60,24 @@
when (state) {
is OngoingCallModel.NoCall -> OngoingActivityChipModel.Hidden()
is OngoingCallModel.InCall -> {
+ val icon =
+ if (
+ Flags.statusBarCallChipNotificationIcon() &&
+ state.notificationIconView != null
+ ) {
+ OngoingActivityChipModel.ChipIcon.StatusBarView(
+ state.notificationIconView
+ )
+ } else {
+ OngoingActivityChipModel.ChipIcon.Basic(phoneIcon)
+ }
+
// This block mimics OngoingCallController#updateChip.
if (state.startTimeMs <= 0L) {
// If the start time is invalid, don't show a timer and show just an
// icon. See b/192379214.
OngoingActivityChipModel.Shown.IconOnly(
- icon = phoneIcon,
+ icon = icon,
colors = ColorsModel.Themed,
getOnClickListener(state),
)
@@ -73,7 +86,7 @@
state.startTimeMs - systemClock.currentTimeMillis() +
systemClock.elapsedRealtime()
OngoingActivityChipModel.Shown.Timer(
- icon = phoneIcon,
+ icon = icon,
colors = ColorsModel.Themed,
startTimeMs = startTimeInElapsedRealtime,
getOnClickListener(state),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index d9b0504..cf4e707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -190,12 +190,14 @@
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
- Icon.Resource(
- CAST_TO_OTHER_DEVICE_ICON,
- // This string is "Casting screen"
- ContentDescription.Resource(
- R.string.cast_screen_to_other_device_chip_accessibility_label,
- ),
+ OngoingActivityChipModel.ChipIcon.Basic(
+ Icon.Resource(
+ CAST_TO_OTHER_DEVICE_ICON,
+ // This string is "Casting screen"
+ ContentDescription.Resource(
+ R.string.cast_screen_to_other_device_chip_accessibility_label,
+ ),
+ )
),
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
@@ -213,10 +215,12 @@
private fun createIconOnlyCastChip(deviceName: String?): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.IconOnly(
icon =
- Icon.Resource(
- CAST_TO_OTHER_DEVICE_ICON,
- // This string is just "Casting"
- ContentDescription.Resource(R.string.accessibility_casting),
+ OngoingActivityChipModel.ChipIcon.Basic(
+ Icon.Resource(
+ CAST_TO_OTHER_DEVICE_ICON,
+ // This string is just "Casting"
+ ContentDescription.Resource(R.string.accessibility_casting),
+ )
),
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 191c221..c5f78d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -21,11 +21,11 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediaprojection.MediaProjectionUtils.packageHasCastingCapabilities
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
-import com.android.systemui.util.Utils
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -60,7 +60,7 @@
}
is MediaProjectionState.Projecting -> {
val type =
- if (isProjectionToOtherDevice(state.hostPackage)) {
+ if (packageHasCastingCapabilities(packageManager, state.hostPackage)) {
ProjectionChipModel.Type.CAST_TO_OTHER_DEVICE
} else {
ProjectionChipModel.Type.SHARE_TO_APP
@@ -86,19 +86,6 @@
scope.launch { mediaProjectionRepository.stopProjecting() }
}
- /**
- * Returns true iff projecting to the given [packageName] means that we're projecting to a
- * *different* device (as opposed to projecting to some application on *this* device).
- */
- private fun isProjectionToOtherDevice(packageName: String?): Boolean {
- // The [isHeadlessRemoteDisplayProvider] check approximates whether a projection is to a
- // different device or the same device, because headless remote display packages are the
- // only kinds of packages that do cast-to-other-device. This isn't exactly perfect,
- // because it means that any projection by those headless remote display packages will be
- // marked as going to a different device, even if that isn't always true. See b/321078669.
- return Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)
- }
-
companion object {
private const val TAG = "MediaProjection"
}
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 fcf3de4..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) {
@@ -78,11 +80,13 @@
is ScreenRecordChipModel.Recording -> {
OngoingActivityChipModel.Shown.Timer(
icon =
- Icon.Resource(
- ICON,
- ContentDescription.Resource(
- R.string.screenrecord_ongoing_screen_only,
- ),
+ OngoingActivityChipModel.ChipIcon.Basic(
+ Icon.Resource(
+ ICON,
+ ContentDescription.Resource(
+ R.string.screenrecord_ongoing_screen_only,
+ ),
+ )
),
colors = ColorsModel.Red,
startTimeMs = systemClock.elapsedRealtime(),
@@ -103,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/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 85973fc..7897f93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -110,9 +110,11 @@
): OngoingActivityChipModel.Shown {
return OngoingActivityChipModel.Shown.Timer(
icon =
- Icon.Resource(
- SHARE_TO_APP_ICON,
- ContentDescription.Resource(R.string.share_to_app_chip_accessibility_label),
+ OngoingActivityChipModel.ChipIcon.Basic(
+ Icon.Resource(
+ SHARE_TO_APP_ICON,
+ ContentDescription.Resource(R.string.share_to_app_chip_accessibility_label),
+ )
),
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
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/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 17cf60b..26a2f91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -17,7 +17,9 @@
package com.android.systemui.statusbar.chips.ui.model
import android.view.View
+import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.StatusBarIconView
/** Model representing the display of an ongoing activity as a chip in the status bar. */
sealed class OngoingActivityChipModel {
@@ -38,7 +40,7 @@
/** This chip should be shown with the given information. */
abstract class Shown(
/** The icon to show on the chip. If null, no icon will be shown. */
- open val icon: Icon?,
+ open val icon: ChipIcon?,
/** What colors to use for the chip. */
open val colors: ColorsModel,
/**
@@ -50,7 +52,7 @@
/** This chip shows only an icon and nothing else. */
data class IconOnly(
- override val icon: Icon,
+ override val icon: ChipIcon,
override val colors: ColorsModel,
override val onClickListener: View.OnClickListener?,
) : Shown(icon, colors, onClickListener) {
@@ -59,7 +61,7 @@
/** The chip shows a timer, counting up from [startTimeMs]. */
data class Timer(
- override val icon: Icon,
+ override val icon: ChipIcon,
override val colors: ColorsModel,
/**
* The time this event started, used to show the timer.
@@ -88,4 +90,23 @@
override val logName = "Shown.Countdown"
}
}
+
+ /** Represents an icon to show on the chip. */
+ sealed interface ChipIcon {
+ /**
+ * The icon is a custom icon, which is set on [impl]. The icon was likely created by an
+ * external app.
+ */
+ data class StatusBarView(val impl: StatusBarIconView) : ChipIcon {
+ init {
+ check(Flags.statusBarCallChipNotificationIcon()) {
+ "OngoingActivityChipModel.ChipIcon.StatusBarView created even though " +
+ "Flags.statusBarCallChipNotificationIcon is not enabled"
+ }
+ }
+ }
+
+ /** The icon is a basic resource or drawable icon that System UI created internally. */
+ data class Basic(val impl: Icon) : ChipIcon
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 9eb9ed5..2930de2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -26,7 +26,6 @@
import androidx.core.animation.Animator
import com.android.app.animation.Interpolators
import com.android.internal.annotations.GuardedBy
-import com.android.systemui.Flags.privacyDotUnfoldWrongCornerFix
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -45,10 +44,10 @@
import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
import com.android.systemui.util.leak.RotationUtils.Rotation
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Understands how to keep the persistent privacy dot in the corner of the screen in
@@ -61,12 +60,13 @@
* Views will match the status bar top padding and status bar height so that the dot can appear to
* reside directly after the status bar system contents (basically after the battery).
*
- * NOTE: any operation that modifies views directly must run on the provided executor, because
- * these views are owned by ScreenDecorations and it runs in its own thread
+ * NOTE: any operation that modifies views directly must run on the provided executor, because these
+ * views are owned by ScreenDecorations and it runs in its own thread
*/
-
@SysUISingleton
-open class PrivacyDotViewController @Inject constructor(
+open class PrivacyDotViewController
+@Inject
+constructor(
@Main private val mainExecutor: Executor,
@Application scope: CoroutineScope,
private val stateController: StatusBarStateController,
@@ -90,6 +90,7 @@
field = value
scheduleUpdate()
}
+
private val lock = Object()
private var cancelRunnable: Runnable? = null
@@ -106,46 +107,48 @@
get() = field
init {
- contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
- override fun onStatusBarContentInsetsChanged() {
- dlog("onStatusBarContentInsetsChanged: ")
- setNewLayoutRects()
+ contentInsetsProvider.addCallback(
+ object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ dlog("onStatusBarContentInsetsChanged: ")
+ setNewLayoutRects()
+ }
}
- })
+ )
- configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
- override fun onLayoutDirectionChanged(isRtl: Boolean) {
- uiExecutor?.execute {
- // If rtl changed, hide all dotes until the next state resolves
- setCornerVisibilities(View.INVISIBLE)
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onLayoutDirectionChanged(isRtl: Boolean) {
+ uiExecutor?.execute {
+ // If rtl changed, hide all dotes until the next state resolves
+ setCornerVisibilities(View.INVISIBLE)
- synchronized(this) {
- val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
- nextViewState = nextViewState.copy(
- layoutRtl = isRtl,
- designatedCorner = corner
- )
+ synchronized(this) {
+ val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
+ nextViewState =
+ nextViewState.copy(layoutRtl = isRtl, designatedCorner = corner)
+ }
}
}
}
- })
+ )
- stateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onExpandedChanged(isExpanded: Boolean) {
- updateStatusBarState()
- }
+ stateController.addCallback(
+ object : StatusBarStateController.StateListener {
+ override fun onExpandedChanged(isExpanded: Boolean) {
+ updateStatusBarState()
+ }
- override fun onStateChanged(newState: Int) {
- updateStatusBarState()
+ override fun onStateChanged(newState: Int) {
+ updateStatusBarState()
+ }
}
- })
+ )
scope.launch {
shadeInteractor?.isQsExpanded?.collect { isQsExpanded ->
dlog("setQsExpanded $isQsExpanded")
- synchronized(lock) {
- nextViewState = nextViewState.copy(qsExpanded = isQsExpanded)
- }
+ synchronized(lock) { nextViewState = nextViewState.copy(qsExpanded = isQsExpanded) }
}
}
}
@@ -179,11 +182,13 @@
val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot)
synchronized(lock) {
- nextViewState = nextViewState.copy(
+ nextViewState =
+ nextViewState.copy(
rotation = rot,
paddingTop = paddingTop,
designatedCorner = newCorner,
- cornerIndex = index)
+ cornerIndex = index
+ )
}
}
@@ -192,14 +197,14 @@
dot.clearAnimation()
if (animate) {
dot.animate()
- .setDuration(DURATION)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .alpha(0f)
- .withEndAction {
- dot.visibility = View.INVISIBLE
- showingListener?.onPrivacyDotHidden(dot)
- }
- .start()
+ .setDuration(DURATION)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .alpha(0f)
+ .withEndAction {
+ dot.visibility = View.INVISIBLE
+ showingListener?.onPrivacyDotHidden(dot)
+ }
+ .start()
} else {
dot.visibility = View.INVISIBLE
showingListener?.onPrivacyDotHidden(dot)
@@ -213,10 +218,10 @@
dot.visibility = View.VISIBLE
dot.alpha = 0f
dot.animate()
- .alpha(1f)
- .setDuration(DURATION)
- .setInterpolator(Interpolators.ALPHA_IN)
- .start()
+ .alpha(1f)
+ .setDuration(DURATION)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .start()
} else {
dot.visibility = View.VISIBLE
dot.alpha = 1f
@@ -241,9 +246,9 @@
}
// Set the dot's view gravity to hug the status bar
- (corner.requireViewById<View>(R.id.privacy_dot)
- .layoutParams as FrameLayout.LayoutParams)
- .gravity = rotatedCorner.innerGravity()
+ (corner.requireViewById<View>(R.id.privacy_dot).layoutParams
+ as FrameLayout.LayoutParams)
+ .gravity = rotatedCorner.innerGravity()
}
}
@@ -353,10 +358,7 @@
clearAnimation()
visibility = View.VISIBLE
alpha = 0f
- animate()
- .alpha(1.0f)
- .setDuration(300)
- .start()
+ animate().alpha(1.0f).setDuration(300).start()
}
}
}
@@ -405,15 +407,21 @@
private fun widthForCorner(corner: Int, left: Int, right: Int): Int {
return when (corner) {
- TOP_LEFT, BOTTOM_LEFT -> left
- TOP_RIGHT, BOTTOM_RIGHT -> right
+ TOP_LEFT,
+ BOTTOM_LEFT -> left
+ TOP_RIGHT,
+ BOTTOM_RIGHT -> right
else -> throw IllegalArgumentException("Unknown corner")
}
}
fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
- if (this::tl.isInitialized && this::tr.isInitialized &&
- this::bl.isInitialized && this::br.isInitialized) {
+ if (
+ this::tl.isInitialized &&
+ this::tr.isInitialized &&
+ this::bl.isInitialized &&
+ this::br.isInitialized
+ ) {
if (tl == topLeft && tr == topRight && bl == bottomLeft && br == bottomRight) {
return
}
@@ -430,19 +438,17 @@
val index = dc.cornerIndex()
- mainExecutor.execute {
- animationScheduler.addCallback(systemStatusAnimationCallback)
- }
+ mainExecutor.execute { animationScheduler.addCallback(systemStatusAnimationCallback) }
val left = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_SEASCAPE)
val top = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_NONE)
val right = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_LANDSCAPE)
- val bottom = contentInsetsProvider
- .getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN)
+ val bottom = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN)
val paddingTop = contentInsetsProvider.getStatusBarPaddingTop()
synchronized(lock) {
- nextViewState = nextViewState.copy(
+ nextViewState =
+ nextViewState.copy(
viewInitialized = true,
designatedCorner = dc,
cornerIndex = index,
@@ -452,14 +458,12 @@
upsideDownRect = bottom,
paddingTop = paddingTop,
layoutRtl = rtl
- )
+ )
}
}
private fun updateStatusBarState() {
- synchronized(lock) {
- nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs())
- }
+ synchronized(lock) { nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs()) }
}
/**
@@ -469,16 +473,14 @@
@GuardedBy("lock")
private fun isShadeInQs(): Boolean {
return (stateController.isExpanded && stateController.state == SHADE) ||
- (stateController.state == SHADE_LOCKED)
+ (stateController.state == SHADE_LOCKED)
}
private fun scheduleUpdate() {
dlog("scheduleUpdate: ")
cancelRunnable?.run()
- cancelRunnable = uiExecutor?.executeDelayed({
- processNextViewState()
- }, 100)
+ cancelRunnable = uiExecutor?.executeDelayed({ processNextViewState() }, 100)
}
@UiThread
@@ -486,9 +488,7 @@
dlog("processNextViewState: ")
val newState: ViewState
- synchronized(lock) {
- newState = nextViewState.copy()
- }
+ synchronized(lock) { newState = nextViewState.copy() }
resolveState(newState)
}
@@ -508,7 +508,7 @@
val designatedCornerChanged = state.designatedCorner != currentViewState.designatedCorner
val rotationChanged = state.rotation != currentViewState.rotation
- if (rotationChanged || (designatedCornerChanged && privacyDotUnfoldWrongCornerFix())) {
+ if (rotationChanged || designatedCornerChanged) {
// A rotation has started, hide the views to avoid flicker
updateRotations(state.rotation, state.paddingTop)
}
@@ -545,27 +545,29 @@
}
private val systemStatusAnimationCallback: SystemStatusAnimationCallback =
- object : SystemStatusAnimationCallback {
- override fun onSystemStatusAnimationTransitionToPersistentDot(
- contentDescr: String?
- ): Animator? {
- synchronized(lock) {
- nextViewState = nextViewState.copy(
- systemPrivacyEventIsActive = true,
- contentDescription = contentDescr)
+ object : SystemStatusAnimationCallback {
+ override fun onSystemStatusAnimationTransitionToPersistentDot(
+ contentDescr: String?
+ ): Animator? {
+ synchronized(lock) {
+ nextViewState =
+ nextViewState.copy(
+ systemPrivacyEventIsActive = true,
+ contentDescription = contentDescr
+ )
+ }
+
+ return null
}
- return null
- }
+ override fun onHidePersistentDot(): Animator? {
+ synchronized(lock) {
+ nextViewState = nextViewState.copy(systemPrivacyEventIsActive = false)
+ }
- override fun onHidePersistentDot(): Animator? {
- synchronized(lock) {
- nextViewState = nextViewState.copy(systemPrivacyEventIsActive = false)
+ return null
}
-
- return null
}
- }
private fun View?.cornerIndex(): Int {
if (this != null) {
@@ -579,8 +581,7 @@
val left = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_SEASCAPE)
val top = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_NONE)
val right = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_LANDSCAPE)
- val bottom = contentInsetsProvider
- .getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN)
+ val bottom = contentInsetsProvider.getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN)
return listOf(left, top, right, bottom)
}
@@ -589,17 +590,19 @@
val rects = getLayoutRects()
synchronized(lock) {
- nextViewState = nextViewState.copy(
+ nextViewState =
+ nextViewState.copy(
seascapeRect = rects[0],
portraitRect = rects[1],
landscapeRect = rects[2],
upsideDownRect = rects[3]
- )
+ )
}
}
interface ShowingListener {
fun onPrivacyDotShown(v: View?)
+
fun onPrivacyDotHidden(v: View?)
}
}
@@ -647,22 +650,18 @@
data class ViewState(
val viewInitialized: Boolean = false,
-
val systemPrivacyEventIsActive: Boolean = false,
val shadeExpanded: Boolean = false,
val qsExpanded: Boolean = false,
-
val portraitRect: Rect? = null,
val landscapeRect: Rect? = null,
val upsideDownRect: Rect? = null,
val seascapeRect: Rect? = null,
val layoutRtl: Boolean = false,
-
val rotation: Int = 0,
val paddingTop: Int = 0,
val cornerIndex: Int = -1,
val designatedCorner: View? = null,
-
val contentDescription: String? = null
) {
fun shouldShowDot(): Boolean {
@@ -671,11 +670,11 @@
fun needsLayout(other: ViewState): Boolean {
return rotation != other.rotation ||
- layoutRtl != other.layoutRtl ||
- portraitRect != other.portraitRect ||
- landscapeRect != other.landscapeRect ||
- upsideDownRect != other.upsideDownRect ||
- seascapeRect != other.seascapeRect
+ layoutRtl != other.layoutRtl ||
+ portraitRect != other.portraitRect ||
+ landscapeRect != other.landscapeRect ||
+ upsideDownRect != other.upsideDownRect ||
+ seascapeRect != other.seascapeRect
}
fun contentRectForRotation(@Rotation rot: Int): Rect {
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/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 4a895c0..c52632e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -20,6 +20,7 @@
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER;
@@ -54,6 +55,7 @@
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import javax.inject.Inject;
@@ -253,10 +255,11 @@
params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseMinimized(isMinimized);
-
- if (screenshareNotificationHiding()
+ boolean needsRedaction = screenshareNotificationHiding()
? inflaterParams.getNeedsRedaction()
- : mNotificationLockscreenUserManager.needsRedaction(entry)) {
+ : mNotificationLockscreenUserManager.needsRedaction(entry);
+
+ if (needsRedaction) {
params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
} else {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
@@ -272,6 +275,14 @@
}
}
+ if (LockscreenOtpRedaction.isEnabled()) {
+ if (inflaterParams.isChildInGroup() && needsRedaction) {
+ params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
+ } else {
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
+ }
+ }
+
if (AsyncGroupHeaderViewInflation.isEnabled()) {
if (inflaterParams.isGroupSummary()) {
params.requireContentViews(FLAG_GROUP_SUMMARY_HEADER);
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/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 5d2d56a..1008451 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -25,6 +25,8 @@
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -128,8 +130,14 @@
return result
}
- private fun NotificationEntry.toModel(): ActiveNotificationModel =
- existingModels.createOrReuse(
+ private fun NotificationEntry.toModel(): ActiveNotificationModel {
+ val statusBarChipIcon =
+ if (Flags.statusBarCallChipNotificationIcon()) {
+ icons.statusBarChipIcon
+ } else {
+ null
+ }
+ return existingModels.createOrReuse(
key = key,
groupKey = sbn.groupKey,
whenTime = sbn.notification.`when`,
@@ -142,6 +150,7 @@
aodIcon = icons.aodIcon?.sourceIcon,
shelfIcon = icons.shelfIcon?.sourceIcon,
statusBarIcon = icons.statusBarIcon?.sourceIcon,
+ statusBarChipIconView = statusBarChipIcon,
uid = sbn.uid,
packageName = sbn.packageName,
contentIntent = sbn.notification.contentIntent,
@@ -150,6 +159,7 @@
bucket = bucket,
callType = sbn.toCallType(),
)
+ }
}
private fun ActiveNotificationsStore.createOrReuse(
@@ -165,6 +175,7 @@
aodIcon: Icon?,
shelfIcon: Icon?,
statusBarIcon: Icon?,
+ statusBarChipIconView: StatusBarIconView?,
uid: Int,
packageName: String,
contentIntent: PendingIntent?,
@@ -187,6 +198,7 @@
aodIcon = aodIcon,
shelfIcon = shelfIcon,
statusBarIcon = statusBarIcon,
+ statusBarChipIconView = statusBarChipIconView,
uid = uid,
instanceId = instanceId,
isGroupSummary = isGroupSummary,
@@ -209,6 +221,7 @@
aodIcon = aodIcon,
shelfIcon = shelfIcon,
statusBarIcon = statusBarIcon,
+ statusBarChipIconView = statusBarChipIconView,
uid = uid,
instanceId = instanceId,
isGroupSummary = isGroupSummary,
@@ -232,6 +245,7 @@
aodIcon: Icon?,
shelfIcon: Icon?,
statusBarIcon: Icon?,
+ statusBarChipIconView: StatusBarIconView?,
uid: Int,
packageName: String,
contentIntent: PendingIntent?,
@@ -253,6 +267,7 @@
aodIcon != this.aodIcon -> false
shelfIcon != this.shelfIcon -> false
statusBarIcon != this.statusBarIcon -> false
+ statusBarChipIconView != this.statusBarChipIconView -> false
uid != this.uid -> false
instanceId != this.instanceId -> false
isGroupSummary != this.isGroupSummary -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 331d3cc..dc6ab41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -123,6 +123,13 @@
// Construct the status bar icon view.
val sbIcon = iconBuilder.createIconView(entry)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ val sbChipIcon: StatusBarIconView?
+ if (Flags.statusBarCallChipNotificationIcon()) {
+ sbChipIcon = iconBuilder.createIconView(entry)
+ sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
+ } else {
+ sbChipIcon = null
+ }
// Construct the shelf icon view.
val shelfIcon = iconBuilder.createIconView(entry)
@@ -139,9 +146,19 @@
try {
setIcon(entry, normalIconDescriptor, sbIcon)
+ if (Flags.statusBarCallChipNotificationIcon() && sbChipIcon != null) {
+ setIcon(entry, normalIconDescriptor, sbChipIcon)
+ }
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
setIcon(entry, sensitiveIconDescriptor, aodIcon)
- entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
+ entry.icons =
+ IconPack.buildPack(
+ sbIcon,
+ sbChipIcon,
+ shelfIcon,
+ aodIcon,
+ entry.icons,
+ )
} catch (e: InflationException) {
entry.icons = IconPack.buildEmptyPack(entry.icons)
throw e
@@ -182,6 +199,11 @@
setIcon(entry, normalIconDescriptor, it)
}
+ entry.icons.statusBarChipIcon?.let {
+ it.setNotification(entry.sbn, notificationContentDescription)
+ setIcon(entry, normalIconDescriptor, it)
+ }
+
entry.icons.shelfIcon?.let {
it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, sensitiveIconDescriptor, it)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index d029ce7..611cebc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -29,6 +29,7 @@
private final boolean mAreIconsAvailable;
@Nullable private final StatusBarIconView mStatusBarIcon;
+ @Nullable private final StatusBarIconView mStatusBarChipIcon;
@Nullable private final StatusBarIconView mShelfIcon;
@Nullable private final StatusBarIconView mAodIcon;
@@ -43,7 +44,7 @@
* haven't been inflated yet or there was an error while inflating them).
*/
public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
- return new IconPack(false, null, null, null, fromSource);
+ return new IconPack(false, null, null, null, null, fromSource);
}
/**
@@ -51,20 +52,23 @@
*/
public static IconPack buildPack(
@NonNull StatusBarIconView statusBarIcon,
+ @Nullable StatusBarIconView statusBarChipIcon,
@NonNull StatusBarIconView shelfIcon,
@NonNull StatusBarIconView aodIcon,
@Nullable IconPack source) {
- return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
+ return new IconPack(true, statusBarIcon, statusBarChipIcon, shelfIcon, aodIcon, source);
}
private IconPack(
boolean areIconsAvailable,
@Nullable StatusBarIconView statusBarIcon,
+ @Nullable StatusBarIconView statusBarChipIcon,
@Nullable StatusBarIconView shelfIcon,
@Nullable StatusBarIconView aodIcon,
@Nullable IconPack source) {
mAreIconsAvailable = areIconsAvailable;
mStatusBarIcon = statusBarIcon;
+ mStatusBarChipIcon = statusBarChipIcon;
mShelfIcon = shelfIcon;
mAodIcon = aodIcon;
if (source != null) {
@@ -79,6 +83,17 @@
}
/**
+ * The version of the notification icon that appears inside a chip within the status bar.
+ *
+ * Separate from {@link #getStatusBarIcon()} so that we don't have to worry about detaching and
+ * re-attaching the same view when the chip appears and hides.
+ */
+ @Nullable
+ public StatusBarIconView getStatusBarChipIcon() {
+ return mStatusBarChipIcon;
+ }
+
+ /**
* The version of the icon that appears in the "shelf" at the bottom of the notification shade.
* In general, this icon also appears somewhere on the notification and is "sucked" into the
* shelf as the scrolls beyond it.
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/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index a6ca3ab..17f401a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -19,6 +19,9 @@
import android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
import android.app.Notification
import android.app.Notification.BubbleMetadata
+import android.app.Notification.CATEGORY_ALARM
+import android.app.Notification.CATEGORY_CAR_EMERGENCY
+import android.app.Notification.CATEGORY_CAR_WARNING
import android.app.Notification.CATEGORY_EVENT
import android.app.Notification.CATEGORY_REMINDER
import android.app.Notification.VISIBILITY_PRIVATE
@@ -42,6 +45,7 @@
import android.service.notification.Flags
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -307,6 +311,9 @@
ALLOW_CALLSTYLE,
ALLOW_CATEGORY_REMINDER,
ALLOW_CATEGORY_EVENT,
+ ALLOW_CATEGORY_ALARM,
+ ALLOW_CATEGORY_CAR_EMERGENCY,
+ ALLOW_CATEGORY_CAR_WARNING,
ALLOW_FSI_WITH_PERMISSION_ON,
ALLOW_COLORIZED,
ALLOW_EMERGENCY,
@@ -333,8 +340,13 @@
@UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
AVALANCHE_SUPPRESSOR_HUN_ALLOWED_COLORIZED(1832),
@UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
- AVALANCHE_SUPPRESSOR_HUN_ALLOWED_EMERGENCY(1833);
-
+ AVALANCHE_SUPPRESSOR_HUN_ALLOWED_EMERGENCY(1833),
+ @UiEvent(doc = "HUN allowed during avalanche because it is an alarm.")
+ AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_ALARM(1867),
+ @UiEvent(doc = "HUN allowed during avalanche because it is a car emergency.")
+ AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY(1868),
+ @UiEvent(doc = "HUN allowed during avalanche because it is a car warning")
+ AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_WARNING(1869);
override fun getId(): Int {
return id
}
@@ -423,6 +435,22 @@
return State.ALLOW_CATEGORY_REMINDER
}
+ if (entry.sbn.notification.category == CATEGORY_ALARM) {
+ uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_ALARM)
+ return State.ALLOW_CATEGORY_ALARM
+ }
+
+ if (entry.sbn.notification.category == CATEGORY_CAR_EMERGENCY) {
+ uiEventLogger.log(
+ AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY)
+ return State.ALLOW_CATEGORY_CAR_EMERGENCY
+ }
+
+ if (entry.sbn.notification.category == CATEGORY_CAR_WARNING) {
+ uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_WARNING)
+ return State.ALLOW_CATEGORY_CAR_WARNING
+ }
+
if (entry.sbn.notification.category == CATEGORY_EVENT) {
uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_EVENT)
return State.ALLOW_CATEGORY_EVENT
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/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 06af980..9d13a17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -102,6 +102,7 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
@@ -997,6 +998,9 @@
}
mNotificationParent = isChildInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(isChildInGroup);
+ if (LockscreenOtpRedaction.isEnabled()) {
+ mPublicLayout.setIsChildInGroup(isChildInGroup);
+ }
updateBackgroundForGroupState();
updateClickAndFocus();
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/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index ba1cfcc..0738a03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -46,7 +46,7 @@
import java.util.Objects;
/**
- * A hybrid view which may contain information about one ore more conversations.
+ * A hybrid view which may contain information about one or more conversations.
*/
public class HybridConversationNotificationView extends HybridNotificationView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index af5117e..8c80fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -56,8 +56,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -196,8 +196,8 @@
result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
+ boolean isConversation = entry.getRanking().isConversation();
if (AsyncHybridViewInflation.isEnabled()) {
- boolean isConversation = entry.getRanking().isConversation();
Notification.MessagingStyle messagingStyle = null;
if (isConversation) {
messagingStyle = mConversationProcessor
@@ -210,8 +210,22 @@
builder,
row.getContext()
);
- result.mInflatedSingleLineViewHolder =
- SingleLineViewInflater.inflateSingleLineViewHolder(
+ result.mInflatedSingleLineView =
+ SingleLineViewInflater.inflatePrivateSingleLineView(
+ isConversation,
+ reInflateFlags,
+ entry,
+ row.getContext(),
+ mLogger
+ );
+ }
+
+ if (LockscreenOtpRedaction.isEnabled()) {
+ result.mPublicInflatedSingleLineViewModel =
+ SingleLineViewInflater.inflateRedactedSingleLineViewModel(row.getContext(),
+ isConversation);
+ result.mPublicInflatedSingleLineView =
+ SingleLineViewInflater.inflatePublicSingleLineView(
isConversation,
reInflateFlags,
entry,
@@ -291,6 +305,16 @@
row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
});
break;
+ case FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE:
+ if (LockscreenOtpRedaction.isEnabled()) {
+ row.getPublicLayout()
+ .performWhenContentInactive(VISIBLE_TYPE_SINGLELINE, () -> {
+ row.getPublicLayout().setSingleLineView(null);
+ mRemoteViewCache.removeCachedView(entry,
+ FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
+ });
+ }
+ break;
case FLAG_CONTENT_VIEW_PUBLIC:
row.getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> {
row.getPublicLayout().setContractedChild(null);
@@ -334,6 +358,10 @@
row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
}
if (AsyncHybridViewInflation.isEnabled()
+ && (contentViews & FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE) != 0) {
+ row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
+ }
+ if (LockscreenOtpRedaction.isEnabled()
&& (contentViews & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
}
@@ -935,19 +963,21 @@
if (AsyncHybridViewInflation.isEnabled()
&& (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
- HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+ HybridNotificationView view = result.mInflatedSingleLineView;
SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
- if (viewHolder != null && viewModel != null) {
- if (viewModel.isConversation()) {
- SingleLineConversationViewBinder.bind(
- result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder
- );
- } else {
- SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder);
- }
- privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+ if (view != null && viewModel != null) {
+ SingleLineViewBinder.bind(viewModel, view);
+ privateLayout.setSingleLineView(result.mInflatedSingleLineView);
+ }
+ }
+
+ if (LockscreenOtpRedaction.isEnabled()
+ && (reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE) != 0) {
+ HybridNotificationView view = result.mPublicInflatedSingleLineView;
+ SingleLineViewModel viewModel = result.mPublicInflatedSingleLineViewModel;
+ if (view != null && viewModel != null) {
+ SingleLineViewBinder.bind(viewModel, view);
+ publicLayout.setSingleLineView(result.mPublicInflatedSingleLineView);
}
}
@@ -1211,8 +1241,22 @@
recoveredBuilder,
mContext
);
- result.mInflatedSingleLineViewHolder =
- SingleLineViewInflater.inflateSingleLineViewHolder(
+ result.mInflatedSingleLineView =
+ SingleLineViewInflater.inflatePrivateSingleLineView(
+ isConversation,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ mLogger
+ );
+ }
+
+ if (LockscreenOtpRedaction.isEnabled()) {
+ result.mPublicInflatedSingleLineViewModel =
+ SingleLineViewInflater.inflateRedactedSingleLineViewModel(mContext,
+ isConversation);
+ result.mPublicInflatedSingleLineView =
+ SingleLineViewInflater.inflatePublicSingleLineView(
isConversation,
mReInflateFlags,
mEntry,
@@ -1347,8 +1391,13 @@
// ViewModel for SingleLineView, holds the UI State
SingleLineViewModel mInflatedSingleLineViewModel;
+ // ViewModel for public SingleLineView, holds the UI State
+ SingleLineViewModel mPublicInflatedSingleLineViewModel;
// Inflated SingleLineViewHolder, SingleLineView that lacks the UI State
- HybridNotificationView mInflatedSingleLineViewHolder;
+ HybridNotificationView mInflatedSingleLineView;
+ // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State for the public
+ // single line view
+ HybridNotificationView mPublicInflatedSingleLineView;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 3f46902..7119145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -113,7 +113,8 @@
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
- private HybridNotificationView mSingleLineView;
+ @VisibleForTesting
+ protected HybridNotificationView mSingleLineView;
@Nullable public DisposableHandle mContractedBinderHandle;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index fe0ee52..07384af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -89,6 +89,7 @@
FLAG_CONTENT_VIEW_SINGLE_LINE,
FLAG_GROUP_SUMMARY_HEADER,
FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
FLAG_CONTENT_VIEW_ALL})
@interface InflationFlag {}
/**
@@ -105,7 +106,7 @@
*/
int FLAG_CONTENT_VIEW_HEADS_UP = 1 << 2;
/**
- * The public view. This is a version of the contracted view that hides sensitive
+ * The public view. This is a version of the contracted view that hides sensitive
* information and is used on the lock screen if we determine that the notification's
* content should be hidden.
*/
@@ -126,7 +127,14 @@
*/
int FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER = 1 << 6;
- int FLAG_CONTENT_VIEW_ALL = (1 << 7) - 1;
+ /**
+ * The public single line view. This is a version of the contracted view that hides sensitive
+ * information and is used on the lock screen if we determine that the notification's
+ * content should be hidden, and the notification is shown as a child in a group.
+ */
+ int FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE = 1 << 7;
+
+ int FLAG_CONTENT_VIEW_ALL = (1 << 8) - 1;
/**
* Parameters for content view binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index 492d802..a5cd2a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -55,6 +55,7 @@
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
@@ -63,11 +64,11 @@
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
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
import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
@@ -202,7 +203,19 @@
if (AsyncHybridViewInflation.isEnabled) {
result.inflatedSingleLineView =
result.contentModel.singleLineViewModel?.let { viewModel ->
- SingleLineViewInflater.inflateSingleLineViewHolder(
+ SingleLineViewInflater.inflatePrivateSingleLineView(
+ viewModel.isConversation(),
+ reInflateFlags,
+ entry,
+ systemUIContext,
+ logger,
+ )
+ }
+ }
+ if (LockscreenOtpRedaction.isEnabled) {
+ result.inflatedPublicSingleLineView =
+ result.contentModel.publicSingleLineViewModel?.let { viewModel ->
+ SingleLineViewInflater.inflatePublicSingleLineView(
viewModel.isConversation(),
reInflateFlags,
entry,
@@ -294,6 +307,13 @@
}
}
}
+ FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE -> {
+ if (LockscreenOtpRedaction.isEnabled) {
+ row.publicLayout.performWhenContentInactive(VISIBLE_TYPE_SINGLELINE) {
+ row.publicLayout.setSingleLineView(null)
+ }
+ }
+ }
else -> {}
}
}
@@ -327,6 +347,12 @@
) {
row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE)
}
+ if (
+ LockscreenOtpRedaction.isEnabled &&
+ contentViews and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
+ ) {
+ row.publicLayout.removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE)
+ }
}
/**
@@ -449,7 +475,7 @@
logger.logAsyncTaskProgress(entry, "inflating single line view")
inflationProgress.inflatedSingleLineView =
inflationProgress.contentModel.singleLineViewModel?.let {
- SingleLineViewInflater.inflateSingleLineViewHolder(
+ SingleLineViewInflater.inflatePrivateSingleLineView(
it.isConversation(),
reInflateFlags,
entry,
@@ -459,6 +485,20 @@
}
}
+ if (LockscreenOtpRedaction.isEnabled) {
+ logger.logAsyncTaskProgress(entry, "inflating public single line view")
+ inflationProgress.inflatedPublicSingleLineView =
+ inflationProgress.contentModel.publicSingleLineViewModel?.let { viewModel ->
+ SingleLineViewInflater.inflatePublicSingleLineView(
+ viewModel.isConversation(),
+ reInflateFlags,
+ entry,
+ context,
+ logger
+ )
+ }
+ }
+
if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
logger.logAsyncTaskProgress(entry, "inflating RON view")
inflationProgress.richOngoingNotificationViewHolder =
@@ -582,6 +622,8 @@
// Inflated SingleLineView that lacks the UI State
var inflatedSingleLineView: HybridNotificationView? = null
+ // Inflated public SingleLineView that lacks the UI State
+ var inflatedPublicSingleLineView: HybridNotificationView? = null
}
@VisibleForTesting
@@ -706,6 +748,18 @@
)
} else null
+ val publicSingleLineViewModel =
+ if (
+ LockscreenOtpRedaction.isEnabled &&
+ reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
+ ) {
+ logger.logAsyncTaskProgress(entry, "inflating public single line view model")
+ SingleLineViewInflater.inflateRedactedSingleLineViewModel(
+ systemUIContext,
+ entry.ranking.isConversation
+ )
+ } else null
+
val headsUpStatusBarModel =
HeadsUpStatusBarModel(
privateText = builder.getHeadsUpStatusBarText(/* publicMode= */ false),
@@ -716,6 +770,7 @@
NotificationContentModel(
headsUpStatusBarModel = headsUpStatusBarModel,
singleLineViewModel = singleLineViewModel,
+ publicSingleLineViewModel = publicSingleLineViewModel,
richOngoingContentModel = richOngoingContentModel,
)
@@ -1405,15 +1460,23 @@
val singleLineView = result.inflatedSingleLineView
val viewModel = result.contentModel.singleLineViewModel
if (singleLineView != null && viewModel != null) {
- if (viewModel.isConversation()) {
- SingleLineConversationViewBinder.bind(viewModel, singleLineView)
- } else {
- SingleLineViewBinder.bind(viewModel, singleLineView)
- }
+ SingleLineViewBinder.bind(viewModel, singleLineView)
row.privateLayout.setSingleLineView(result.inflatedSingleLineView)
}
}
+ if (
+ LockscreenOtpRedaction.isEnabled &&
+ reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
+ ) {
+ val singleLineView = result.inflatedPublicSingleLineView
+ val viewModel = result.contentModel.publicSingleLineViewModel
+ if (singleLineView != null && viewModel != null) {
+ SingleLineViewBinder.bind(viewModel, singleLineView)
+ row.publicLayout.setSingleLineView(result.inflatedPublicSingleLineView)
+ }
+ }
+
// after updating the content model, set the view, then start the new binder
result.richOngoingNotificationViewHolder?.let { viewHolder ->
row.privateLayout.contractedChild = viewHolder.view
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index 463192c..d67947d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -30,6 +30,7 @@
import com.android.internal.widget.PeopleHelper
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
@@ -107,6 +108,36 @@
)
}
+ @JvmStatic
+ fun inflateRedactedSingleLineViewModel(
+ context: Context,
+ isConversation: Boolean = false
+ ): SingleLineViewModel {
+ val conversationData =
+ if (isConversation) {
+ ConversationData(
+ null,
+ SingleIcon(
+ context.getDrawable(
+ com.android.systemui.res.R.drawable
+ .ic_redacted_notification_single_line_icon
+ )
+ )
+ )
+ } else {
+ null
+ }
+ return SingleLineViewModel(
+ context.getString(
+ com.android.systemui.res.R.string.redacted_notification_single_line_title
+ ),
+ context.getString(
+ com.android.systemui.res.R.string.redacted_notification_single_line_text
+ ),
+ conversationData
+ )
+ }
+
/** load conversation text data from the MessagingStyle of conversation notifications */
private fun MessagingStyle.loadConversationTextData(
systemUiContext: Context
@@ -137,8 +168,8 @@
// We need to find back-up values for those texts if they are needed and empty
return ConversationTextData(
- conversationTitle = conversationTitle
- ?: findBackUpConversationTitle(senderName, systemUiContext),
+ conversationTitle =
+ conversationTitle ?: findBackUpConversationTitle(senderName, systemUiContext),
conversationText = conversationText,
senderName = senderName,
)
@@ -328,12 +359,27 @@
return FacePile(
topIconDrawable = secondLastIcon.loadDrawable(systemUiContext),
bottomIconDrawable = lastIcon.loadDrawable(systemUiContext),
- bottomBackgroundColor = builder.getBackgroundColor(/* isHeader = */ false),
+ bottomBackgroundColor = builder.getBackgroundColor(/* isHeader= */ false),
)
}
@JvmStatic
- fun inflateSingleLineViewHolder(
+ fun inflatePublicSingleLineView(
+ isConversation: Boolean,
+ reinflateFlags: Int,
+ entry: NotificationEntry,
+ context: Context,
+ logger: NotificationRowContentBinderLogger,
+ ): HybridNotificationView? {
+ return if ((reinflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE) == 0) {
+ null
+ } else {
+ inflateSingleLineView(isConversation, reinflateFlags, entry, context, logger)
+ }
+ }
+
+ @JvmStatic
+ fun inflatePrivateSingleLineView(
isConversation: Boolean,
reinflateFlags: Int,
entry: NotificationEntry,
@@ -341,9 +387,21 @@
logger: NotificationRowContentBinderLogger,
): HybridNotificationView? {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
- if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) {
- return null
+ return if ((reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE) == 0) {
+ null
+ } else {
+ inflateSingleLineView(isConversation, reinflateFlags, entry, context, logger)
}
+ }
+
+ private fun inflateSingleLineView(
+ isConversation: Boolean,
+ reinflateFlags: Int,
+ entry: NotificationEntry,
+ context: Context,
+ logger: NotificationRowContentBinderLogger,
+ ): HybridNotificationView? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
logger.logInflateSingleLine(entry, reinflateFlags, isConversation)
logger.logAsyncTaskProgress(entry, "inflating single-line content view")
@@ -356,7 +414,7 @@
if (isConversation)
com.android.systemui.res.R.layout.hybrid_conversation_notification
else com.android.systemui.res.R.layout.hybrid_notification
- view = inflater.inflate(layoutRes, /* root = */ null) as HybridNotificationView
+ view = inflater.inflate(layoutRes, /* root= */ null) as HybridNotificationView
if (view == null) {
Log.wtf(TAG, "Single-line view inflation result is null for entry: ${entry.logKey}")
}
@@ -368,7 +426,7 @@
name: CharSequence?,
uniqueNames: PeopleHelper.NameToPrefixMap? = null
): Icon {
- val layoutColor = getSmallIconColor(/* isHeader = */ false)
+ val layoutColor = getSmallIconColor(/* isHeader= */ false)
if (!name.isNullOrEmpty()) {
val symbol = uniqueNames?.getPrefix(name) ?: ""
return peopleHelper.createAvatarSymbol(
@@ -379,7 +437,7 @@
}
// If name is null, create default avatar with background color
// TODO(b/319829062): Investigate caching default icon for color
- return peopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor)
+ return peopleHelper.createAvatarSymbol(/* name= */ "", /* symbol= */ "", layoutColor)
}
private fun Person.getKeyOrName(): CharSequence? = if (key == null) name else key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/LockscreenOtpRedaction.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/LockscreenOtpRedaction.kt
new file mode 100644
index 0000000..078deb9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/LockscreenOtpRedaction.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import android.app.Flags
+import android.app.Flags.redactSensitiveContentNotificationsOnLockscreen
+import com.android.systemui.flags.FlagToken
+
+/** Helper for reading or using the async hybrid view inflation flag state. */
+object LockscreenOtpRedaction {
+ const val FLAG_NAME = Flags.FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ @JvmStatic
+ inline val isEnabled
+ get() =
+ redactSensitiveContentNotificationsOnLockscreen() && AsyncHybridViewInflation.isEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
index 46010a1..0f9a5a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
@@ -21,6 +21,7 @@
data class NotificationContentModel(
val headsUpStatusBarModel: HeadsUpStatusBarModel,
val singleLineViewModel: SingleLineViewModel? = null,
+ val publicSingleLineViewModel: SingleLineViewModel? = null,
val richOngoingContentModel: RichOngoingContentModel? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
deleted file mode 100644
index 69284bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
+++ /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.systemui.statusbar.notification.row.ui.viewbinder
-
-import com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
-import com.android.systemui.statusbar.notification.row.HybridNotificationView
-import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
-
-object SingleLineConversationViewBinder {
- @JvmStatic
- fun bind(viewModel: SingleLineViewModel, view: HybridNotificationView?) {
- if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
- if (view !is HybridConversationNotificationView || !viewModel.isConversation()) {
- SingleLineViewBinder.bind(viewModel, view)
- return
- }
-
- viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
- view.setText(
- viewModel.titleText,
- viewModel.contentText,
- viewModel.conversationData?.conversationSenderName
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
index 22e10c1..3b0f1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -16,19 +16,32 @@
package com.android.systemui.statusbar.notification.row.ui.viewbinder
+import com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
object SingleLineViewBinder {
@JvmStatic
fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
- // bind the title and content text views
- view?.apply {
- bind(
- /* title = */ viewModel?.titleText,
- /* text = */ viewModel?.contentText,
- /* contentView = */ null
+ if (viewModel?.isConversation() == true && view is HybridConversationNotificationView) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
+
+ viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+ view.setText(
+ viewModel.titleText,
+ viewModel.contentText,
+ viewModel.conversationData?.conversationSenderName
)
+ } else {
+ // bind the title and content text views
+ view?.apply {
+ bind(
+ /* title = */ viewModel?.titleText,
+ /* text = */ viewModel?.contentText,
+ /* contentView = */ null
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 6960791..cf19938 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -17,6 +17,7 @@
import android.app.PendingIntent
import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.stack.PriorityBucket
/**
@@ -59,6 +60,8 @@
val shelfIcon: Icon?,
/** Icon to display in the status bar. */
val statusBarIcon: Icon?,
+ /** Icon to display in the status bar chip. */
+ val statusBarChipIconView: StatusBarIconView?,
/** The notifying app's [packageName]'s uid. */
val uid: Int,
/** The notifying app's packageName. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt
index 3b30c86..1dac8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt
@@ -20,10 +20,13 @@
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for com.android.systemui.Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX */
+/**
+ * Helper for com.android.systemui.Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX, which
+ * now uses FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
+ */
@Suppress("NOTHING_TO_INLINE")
object GroupHunAnimationFix {
- const val FLAG_NAME = Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
/** A token used for dependency declaration */
val token: FlagToken
@@ -32,7 +35,7 @@
/** Return whether the fix is enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationGroupHunRemovalAnimationFix()
+ get() = Flags.notificationAvalancheThrottleHun()
/**
* 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/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 37fdaeb..db8cd57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -30,7 +30,6 @@
import static com.android.systemui.Flags.keyboardShortcutHelperRewrite;
import static com.android.systemui.Flags.lightRevealMigration;
import static com.android.systemui.Flags.newAodTransition;
-import static com.android.systemui.Flags.truncatedStatusBarIconsFix;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -210,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;
@@ -238,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;
@@ -547,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;
@@ -684,7 +681,6 @@
DemoModeController demoModeController,
Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
- NotificationIconAreaController notificationIconAreaController,
BrightnessSliderController.Factory brightnessSliderFactory,
ScreenOffAnimationController screenOffAnimationController,
WallpaperController wallpaperController,
@@ -788,7 +784,6 @@
mUserInfoControllerImpl = userInfoControllerImpl;
mIconPolicy = phoneStatusBarPolicy;
mDemoModeController = demoModeController;
- mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
mWallpaperController = wallpaperController;
mStatusBarSignalPolicy = statusBarSignalPolicy;
@@ -1933,11 +1928,6 @@
mQSPanelController.updateResources();
}
- if (!truncatedStatusBarIconsFix()) {
- if (mStatusBarWindowController != null) {
- mStatusBarWindowController.refreshStatusBarHeight();
- }
- }
if (mShadeSurface != null) {
mShadeSurface.updateResources();
}
@@ -2279,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;
@@ -2658,9 +2651,6 @@
!mDozeServiceHost.isPulsing());
mShadeSurface.setTouchAndAnimationDisabled(disabled);
- if (!NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaController.setAnimationsEnabled(!disabled);
- }
}
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@@ -3059,9 +3049,6 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- if (!NotificationIconContainerRefactor.isEnabled()) {
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
- }
}
@Override
@@ -3079,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/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 6b47ac1..8115c36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Flags.truncatedStatusBarIconsFix;
import android.annotation.Nullable;
import android.content.Context;
@@ -108,9 +107,7 @@
Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock);
if (updateDisplayParameters()) {
updateLayoutForCutout();
- if (truncatedStatusBarIconsFix()) {
- updateWindowHeight();
- }
+ updateWindowHeight();
}
}
@@ -135,9 +132,7 @@
updateLayoutForCutout();
requestLayout();
}
- if (truncatedStatusBarIconsFix()) {
- updateWindowHeight();
- }
+ updateWindowHeight();
}
void onDensityOrFontScaleChanged() {
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 0b8f18e..f8f9b77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -737,7 +737,6 @@
} 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.hide();
mPrimaryBouncerInteractor.show(/* isScrimmed= */ 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/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 7af0666..4368239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -38,6 +38,7 @@
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
@@ -114,6 +115,9 @@
CallNotificationInfo(
entry.sbn.key,
entry.sbn.notification.getWhen(),
+ // In this old listener pattern, we don't have access to the
+ // notification icon.
+ notificationIconView = null,
entry.sbn.notification.contentIntent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
@@ -223,8 +227,15 @@
callNotificationInfo
// This shouldn't happen, but protect against it in case
?: return OngoingCallModel.NoCall
+ val icon =
+ if (Flags.statusBarCallChipNotificationIcon()) {
+ currentInfo.notificationIconView
+ } else {
+ null
+ }
return OngoingCallModel.InCall(
startTimeMs = currentInfo.callStartTime,
+ notificationIconView = icon,
intent = currentInfo.intent,
)
} else {
@@ -260,6 +271,7 @@
CallNotificationInfo(
notifModel.key,
notifModel.whenTime,
+ notifModel.statusBarChipIconView,
notifModel.contentIntent,
notifModel.uid,
isOngoing = true,
@@ -407,6 +419,8 @@
private data class CallNotificationInfo(
val key: String,
val callStartTime: Long,
+ /** The icon set as the [android.app.Notification.getSmallIcon] field. */
+ val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
val uid: Int,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 2c48487..34bff80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone.ongoingcall.shared.model
import android.app.PendingIntent
+import com.android.systemui.statusbar.StatusBarIconView
/** Represents the state of any ongoing calls. */
sealed interface OngoingCallModel {
@@ -31,7 +32,13 @@
* [com.android.systemui.util.time.SystemClock.currentTimeMillis], **not**
* [com.android.systemui.util.time.SystemClock.elapsedRealtime]. This value can be 0 if the
* user has started an outgoing call that hasn't been answered yet - see b/192379214.
+ * @property notificationIconView the [android.app.Notification.getSmallIcon] that's set on the
+ * call notification. We may use this icon in the chip instead of the default phone icon.
* @property intent the intent associated with the call notification.
*/
- data class InCall(val startTimeMs: Long, val intent: PendingIntent?) : OngoingCallModel
+ data class InCall(
+ val startTimeMs: Long,
+ val notificationIconView: StatusBarIconView?,
+ val intent: PendingIntent?,
+ ) : OngoingCallModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index cd442cf..99ed2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -21,7 +21,6 @@
import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
-import android.telephony.TelephonyManager.EXTRA_SPN
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
@@ -97,8 +96,7 @@
fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
- val spn = getStringExtra(EXTRA_SPN)
- val dataSpn = getStringExtra(EXTRA_DATA_SPN)
+ val spn = getStringExtra(EXTRA_DATA_SPN)
val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false)
val plmn = getStringExtra(EXTRA_PLMN)
@@ -114,12 +112,6 @@
}
str.append(spn)
}
- if (showSpn && dataSpn != null) {
- if (str.isNotEmpty()) {
- str.append(separator)
- }
- str.append(dataSpn)
- }
return if (str.isNotEmpty()) NetworkNameModel.IntentDerived(str.toString()) else null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index 16bd7f8..d46aaf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -18,9 +18,12 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.annotation.IdRes
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.Lifecycle
@@ -31,6 +34,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -90,7 +94,7 @@
if (Flags.statusBarScreenSharingChips()) {
val chipView: View = view.requireViewById(R.id.ongoing_activity_chip)
val chipContext = chipView.context
- val chipIconView: ImageView =
+ val chipDefaultIconView: ImageView =
chipView.requireViewById(R.id.ongoing_activity_chip_icon)
val chipTimeView: ChipChronometer =
chipView.requireViewById(R.id.ongoing_activity_chip_time)
@@ -105,16 +109,25 @@
when (chipModel) {
is OngoingActivityChipModel.Shown -> {
// Data
- IconViewBinder.bindNullable(chipModel.icon, chipIconView)
+ setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView)
setChipMainContent(chipModel, chipTextView, chipTimeView)
chipView.setOnClickListener(chipModel.onClickListener)
+ updateChipPadding(
+ chipModel,
+ chipBackgroundView,
+ chipTextView,
+ chipTimeView,
+ )
// Accessibility
setChipAccessibility(chipModel, chipView, chipBackgroundView)
// Colors
val textColor = chipModel.colors.text(chipContext)
- chipIconView.imageTintList = ColorStateList.valueOf(textColor)
+ chipDefaultIconView.imageTintList =
+ ColorStateList.valueOf(textColor)
+ chipBackgroundView.getCustomIconView()?.imageTintList =
+ ColorStateList.valueOf(textColor)
chipTimeView.setTextColor(textColor)
chipTextView.setTextColor(textColor)
(chipBackgroundView.background as GradientDrawable).color =
@@ -151,6 +164,69 @@
}
}
+ private fun setChipIcon(
+ chipModel: OngoingActivityChipModel.Shown,
+ backgroundView: ChipBackgroundContainer,
+ defaultIconView: ImageView,
+ ) {
+ // Always remove any previously set custom icon. If we have a new custom icon, we'll re-add
+ // it.
+ backgroundView.removeView(backgroundView.getCustomIconView())
+
+ when (val icon = chipModel.icon) {
+ null -> {
+ defaultIconView.visibility = View.GONE
+ }
+ is OngoingActivityChipModel.ChipIcon.Basic -> {
+ IconViewBinder.bind(icon.impl, defaultIconView)
+ defaultIconView.visibility = View.VISIBLE
+ }
+ is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
+ // Hide the default icon since we'll show this custom icon instead.
+ defaultIconView.visibility = View.GONE
+
+ // Add the new custom icon:
+ // 1. Set up the right visual params.
+ val iconView = icon.impl
+ with(iconView) {
+ id = CUSTOM_ICON_VIEW_ID
+ // TODO(b/354930838): Update the content description to not include "phone" and
+ // maybe include the app name.
+ contentDescription =
+ context.resources.getString(R.string.ongoing_phone_call_content_description)
+ }
+
+ // 2. If we just reinflated the view, we may need to detach the icon view from the
+ // old chip before we reattach it to the new one.
+ // See also: NotificationIconContainerViewBinder#bindIcons.
+ val currentParent = iconView.parent as? ViewGroup
+ if (currentParent != null && currentParent != backgroundView) {
+ currentParent.removeView(iconView)
+ currentParent.removeTransientView(iconView)
+ }
+
+ // 3: Add the icon as the starting view.
+ backgroundView.addView(
+ iconView,
+ /* index= */ 0,
+ generateCustomIconLayoutParams(iconView),
+ )
+ }
+ }
+ }
+
+ private fun View.getCustomIconView(): StatusBarIconView? {
+ return this.findViewById(CUSTOM_ICON_VIEW_ID)
+ }
+
+ private fun generateCustomIconLayoutParams(iconView: ImageView): FrameLayout.LayoutParams {
+ val customIconSize =
+ iconView.context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_embedded_padding_icon_size
+ )
+ return FrameLayout.LayoutParams(customIconSize, customIconSize)
+ }
+
private fun setChipMainContent(
chipModel: OngoingActivityChipModel.Shown,
chipTextView: TextView,
@@ -180,37 +256,93 @@
chipTimeView.visibility = View.GONE
}
}
- updateChipTextPadding(chipModel, chipTextView, chipTimeView)
}
- private fun updateChipTextPadding(
+ private fun updateChipPadding(
chipModel: OngoingActivityChipModel.Shown,
+ backgroundView: View,
chipTextView: TextView,
chipTimeView: ChipChronometer,
) {
- val requiresPadding = chipModel.icon != null
- if (requiresPadding) {
- chipTextView.addChipTextPaddingStart()
- chipTimeView.addChipTextPaddingStart()
+ if (chipModel.icon != null) {
+ if (chipModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView) {
+ // If the icon is a custom [StatusBarIconView], then it should've come from
+ // `Notification.smallIcon`, which is required to embed its own paddings. We need to
+ // adjust the other paddings to make everything look good :)
+ backgroundView.setBackgroundPaddingForEmbeddedPaddingIcon()
+ chipTextView.setTextPaddingForEmbeddedPaddingIcon()
+ chipTimeView.setTextPaddingForEmbeddedPaddingIcon()
+ } else {
+ backgroundView.setBackgroundPaddingForNormalIcon()
+ chipTextView.setTextPaddingForNormalIcon()
+ chipTimeView.setTextPaddingForNormalIcon()
+ }
} else {
- chipTextView.removeChipTextPaddingStart()
- chipTimeView.removeChipTextPaddingStart()
+ backgroundView.setBackgroundPaddingForNoIcon()
+ chipTextView.setTextPaddingForNoIcon()
+ chipTimeView.setTextPaddingForNoIcon()
}
}
- private fun View.addChipTextPaddingStart() {
+ private fun View.setTextPaddingForEmbeddedPaddingIcon() {
+ val newPaddingEnd =
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_text_end_padding_for_embedded_padding_icon
+ )
+ setPaddingRelative(
+ // The icon should embed enough padding between the icon and time view.
+ /* start= */ 0,
+ this.paddingTop,
+ newPaddingEnd,
+ this.paddingBottom,
+ )
+ }
+
+ private fun View.setTextPaddingForNormalIcon() {
this.setPaddingRelative(
this.context.resources.getDimensionPixelSize(
R.dimen.ongoing_activity_chip_icon_text_padding
),
paddingTop,
- paddingEnd,
+ // The background view will contain the right end padding.
+ /* end= */ 0,
paddingBottom,
)
}
- private fun View.removeChipTextPaddingStart() {
- this.setPaddingRelative(/* start= */ 0, paddingTop, paddingEnd, paddingBottom)
+ private fun View.setTextPaddingForNoIcon() {
+ // The background view will have even start & end paddings, so we don't want the text view
+ // to add any additional padding.
+ this.setPaddingRelative(/* start= */ 0, paddingTop, /* end= */ 0, paddingBottom)
+ }
+
+ private fun View.setBackgroundPaddingForEmbeddedPaddingIcon() {
+ val sidePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
+ )
+ setPaddingRelative(
+ sidePadding,
+ paddingTop,
+ sidePadding,
+ paddingBottom,
+ )
+ }
+
+ private fun View.setBackgroundPaddingForNormalIcon() {
+ val sidePadding =
+ context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_side_padding)
+ setPaddingRelative(
+ sidePadding,
+ paddingTop,
+ sidePadding,
+ paddingBottom,
+ )
+ }
+
+ private fun View.setBackgroundPaddingForNoIcon() {
+ // The padding for the normal icon is also appropriate for no icon.
+ setBackgroundPaddingForNormalIcon()
}
private fun setChipAccessibility(
@@ -269,6 +401,10 @@
)
.start()
}
+
+ companion object {
+ @IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
+ }
}
/** Listener for various events that may affect the status bar's visibility. */
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 9422878f6..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
@@ -23,9 +23,9 @@
import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
import com.android.settingslib.notification.modes.EnableZenModeDialog
import com.android.settingslib.notification.modes.ZenMode
-import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
@@ -50,7 +50,7 @@
@Background val bgDispatcher: CoroutineDispatcher,
private val dialogDelegate: ModesDialogDelegate,
) {
- private val zenDialogMetricsLogger = ZenModeDialogMetricsLogger(context)
+ private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
// Modes that should be displayed in the dialog
private val visibleModes: Flow<List<ZenMode>> =
@@ -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 3dca6fc..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
@@ -16,130 +16,51 @@
package com.android.systemui.touchpad.tutorial.ui.composable
-import android.graphics.ColorFilter
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import androidx.activity.compose.BackHandler
-import androidx.annotation.RawRes
-import androidx.annotation.StringRes
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.togetherWith
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.input.pointer.pointerInteropFilter
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.compose.LottieAnimation
-import com.airbnb.lottie.compose.LottieCompositionSpec
-import com.airbnb.lottie.compose.LottieConstants
-import com.airbnb.lottie.compose.LottieDynamicProperties
-import com.airbnb.lottie.compose.LottieDynamicProperty
-import com.airbnb.lottie.compose.animateLottieCompositionAsState
-import com.airbnb.lottie.compose.rememberLottieComposition
import com.airbnb.lottie.compose.rememberLottieDynamicProperties
-import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
-import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
-
-data class TutorialScreenColors(
- val backgroundColor: Color,
- val successBackgroundColor: Color,
- val titleColor: Color,
- val animationProperties: LottieDynamicProperties
-)
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
@Composable
fun BackGestureTutorialScreen(
onDoneButtonClicked: () -> Unit,
onBack: () -> Unit,
) {
- val screenColors = rememberScreenColors()
- BackHandler(onBack = onBack)
- var gestureState by remember { mutableStateOf(NOT_STARTED) }
- val swipeDistanceThresholdPx =
- LocalContext.current.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.system_gestures_distance_threshold
- )
- val gestureHandler =
- remember(swipeDistanceThresholdPx) {
- TouchpadGestureHandler(
- BackGestureMonitor(
- swipeDistanceThresholdPx,
- gestureStateChangedCallback = { gestureState = it }
+ val screenConfig =
+ TutorialScreenConfig(
+ colors = rememberScreenColors(),
+ strings =
+ TutorialScreenConfig.Strings(
+ titleResId = R.string.touchpad_back_gesture_action_title,
+ bodyResId = R.string.touchpad_back_gesture_guidance,
+ titleSuccessResId = R.string.touchpad_back_gesture_success_title,
+ bodySuccessResId = R.string.touchpad_back_gesture_success_body
),
- )
- }
- TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
- GestureTutorialContent(gestureState, onDoneButtonClicked, screenColors)
- }
-}
-
-@Composable
-private fun TouchpadGesturesHandlingBox(
- gestureHandler: TouchpadGestureHandler,
- gestureState: GestureState,
- modifier: Modifier = Modifier,
- content: @Composable BoxScope.() -> Unit
-) {
- Box(
- modifier =
- modifier
- .fillMaxSize()
- // we need to use pointerInteropFilter because some info about touchpad gestures is
- // only available in MotionEvent
- .pointerInteropFilter(
- onTouchEvent = { event ->
- // FINISHED is the final state so we don't need to process touches anymore
- if (gestureState != FINISHED) {
- gestureHandler.onMotionEvent(event)
- } else {
- false
- }
- }
+ animations =
+ TutorialScreenConfig.Animations(
+ educationResId = R.raw.trackpad_back_edu,
+ successResId = R.raw.trackpad_back_success
)
- ) {
- content()
- }
+ )
+ val gestureMonitorProvider =
+ object : GestureMonitorProvider {
+ override fun createGestureMonitor(
+ gestureDistanceThresholdPx: Int,
+ gestureStateChangedCallback: (GestureState) -> Unit
+ ): TouchpadGestureMonitor {
+ return BackGestureMonitor(gestureDistanceThresholdPx, gestureStateChangedCallback)
+ }
+ }
+ GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
}
@Composable
-private fun rememberScreenColors(): TutorialScreenColors {
+private fun rememberScreenColors(): TutorialScreenConfig.Colors {
val onTertiary = LocalAndroidColorScheme.current.onTertiary
val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
@@ -154,167 +75,12 @@
)
val screenColors =
remember(onTertiaryFixed, surfaceContainer, tertiaryFixedDim, dynamicProperties) {
- TutorialScreenColors(
- backgroundColor = onTertiaryFixed,
- successBackgroundColor = surfaceContainer,
- titleColor = tertiaryFixedDim,
- animationProperties = dynamicProperties,
+ TutorialScreenConfig.Colors(
+ background = onTertiaryFixed,
+ successBackground = surfaceContainer,
+ title = tertiaryFixedDim,
+ animationColors = dynamicProperties,
)
}
return screenColors
}
-
-@Composable
-private fun GestureTutorialContent(
- gestureState: GestureState,
- onDoneButtonClicked: () -> Unit,
- screenColors: TutorialScreenColors
-) {
- val animatedColor by
- animateColorAsState(
- targetValue =
- if (gestureState == FINISHED) screenColors.successBackgroundColor
- else screenColors.backgroundColor,
- animationSpec = tween(durationMillis = 150, easing = LinearEasing),
- label = "backgroundColor"
- )
- Column(
- verticalArrangement = Arrangement.Center,
- modifier =
- Modifier.fillMaxSize()
- .drawBehind { drawRect(animatedColor) }
- .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
- ) {
- Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
- TutorialDescription(
- titleTextId =
- if (gestureState == FINISHED) R.string.touchpad_tutorial_gesture_done
- else R.string.touchpad_back_gesture_action_title,
- titleColor = screenColors.titleColor,
- bodyTextId =
- if (gestureState == FINISHED) R.string.touchpad_back_gesture_finished
- else R.string.touchpad_back_gesture_guidance,
- modifier = Modifier.weight(1f)
- )
- Spacer(modifier = Modifier.width(76.dp))
- TutorialAnimation(
- gestureState,
- screenColors.animationProperties,
- modifier = Modifier.weight(1f).padding(top = 8.dp)
- )
- }
- DoneButton(onDoneButtonClicked = onDoneButtonClicked)
- }
-}
-
-@Composable
-fun TutorialDescription(
- @StringRes titleTextId: Int,
- titleColor: Color,
- @StringRes bodyTextId: Int,
- modifier: Modifier = Modifier
-) {
- Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
- Text(
- text = stringResource(id = titleTextId),
- style = MaterialTheme.typography.displayLarge,
- color = titleColor
- )
- Spacer(modifier = Modifier.height(16.dp))
- Text(
- text = stringResource(id = bodyTextId),
- style = MaterialTheme.typography.bodyLarge,
- color = Color.White
- )
- }
-}
-
-@Composable
-fun TutorialAnimation(
- gestureState: GestureState,
- animationProperties: LottieDynamicProperties,
- modifier: Modifier = Modifier
-) {
- Box(modifier = modifier.fillMaxWidth()) {
- AnimatedContent(
- targetState = gestureState,
- transitionSpec = {
- if (initialState == NOT_STARTED && targetState == IN_PROGRESS) {
- val transitionDurationMillis = 150
- fadeIn(
- animationSpec = tween(transitionDurationMillis, easing = LinearEasing)
- ) togetherWith
- fadeOut(animationSpec = snap(delayMillis = transitionDurationMillis))
- } else {
- // empty transition works because all remaining transitions are from IN_PROGRESS
- // state which shares initial animation frame with both FINISHED and NOT_STARTED
- EnterTransition.None togetherWith ExitTransition.None
- }
- }
- ) { gestureState ->
- @RawRes val successAnimationId = R.raw.trackpad_back_success
- @RawRes val educationAnimationId = R.raw.trackpad_back_edu
- when (gestureState) {
- NOT_STARTED -> EducationAnimation(educationAnimationId, animationProperties)
- IN_PROGRESS -> FrozenSuccessAnimation(successAnimationId, animationProperties)
- FINISHED -> SuccessAnimation(successAnimationId, animationProperties)
- }
- }
- }
-}
-
-@Composable
-private fun FrozenSuccessAnimation(
- @RawRes successAnimationId: Int,
- animationProperties: LottieDynamicProperties
-) {
- val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
- LottieAnimation(
- composition = composition,
- progress = { 0f }, // animation should freeze on 1st frame
- dynamicProperties = animationProperties,
- )
-}
-
-@Composable
-private fun EducationAnimation(
- @RawRes educationAnimationId: Int,
- animationProperties: LottieDynamicProperties
-) {
- val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
- val progress by
- animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
- LottieAnimation(
- composition = composition,
- progress = { progress },
- dynamicProperties = animationProperties,
- )
-}
-
-@Composable
-private fun SuccessAnimation(
- @RawRes successAnimationId: Int,
- animationProperties: LottieDynamicProperties
-) {
- val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
- val progress by animateLottieCompositionAsState(composition, iterations = 1)
- LottieAnimation(
- composition = composition,
- progress = { progress },
- dynamicProperties = animationProperties,
- )
-}
-
-@Composable
-fun rememberColorFilterProperty(
- layerName: String,
- color: Color
-): LottieDynamicProperty<ColorFilter> {
- return rememberLottieDynamicProperty(
- LottieProperty.COLOR_FILTER,
- value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
- // "**" below means match zero or more layers, so ** layerName ** means find layer with that
- // name at any depth
- keyPath = arrayOf("**", layerName, "**")
- )
-}
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
new file mode 100644
index 0000000..9ac2cba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -0,0 +1,303 @@
+/*
+ * 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 android.graphics.ColorFilter
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.activity.compose.BackHandler
+import androidx.annotation.RawRes
+import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.input.pointer.pointerInteropFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
+import com.airbnb.lottie.compose.LottieDynamicProperties
+import com.airbnb.lottie.compose.LottieDynamicProperty
+import com.airbnb.lottie.compose.animateLottieCompositionAsState
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
+import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
+
+interface GestureMonitorProvider {
+ fun createGestureMonitor(
+ gestureDistanceThresholdPx: Int,
+ gestureStateChangedCallback: (GestureState) -> Unit
+ ): TouchpadGestureMonitor
+}
+
+@Composable
+fun GestureTutorialScreen(
+ screenConfig: TutorialScreenConfig,
+ gestureMonitorProvider: GestureMonitorProvider,
+ onDoneButtonClicked: () -> Unit,
+ onBack: () -> Unit,
+) {
+ BackHandler(onBack = onBack)
+ var gestureState by remember { mutableStateOf(NOT_STARTED) }
+ val swipeDistanceThresholdPx =
+ LocalContext.current.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_distance_threshold
+ )
+ val gestureHandler =
+ remember(swipeDistanceThresholdPx) {
+ TouchpadGestureHandler(
+ gestureMonitorProvider.createGestureMonitor(
+ swipeDistanceThresholdPx,
+ gestureStateChangedCallback = { gestureState = it }
+ )
+ )
+ }
+ TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
+ GestureTutorialContent(gestureState, onDoneButtonClicked, screenConfig)
+ }
+}
+
+@Composable
+private fun TouchpadGesturesHandlingBox(
+ gestureHandler: TouchpadGestureHandler,
+ gestureState: GestureState,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit
+) {
+ Box(
+ modifier =
+ modifier
+ .fillMaxSize()
+ // we need to use pointerInteropFilter because some info about touchpad gestures is
+ // only available in MotionEvent
+ .pointerInteropFilter(
+ onTouchEvent = { event ->
+ // FINISHED is the final state so we don't need to process touches anymore
+ if (gestureState == FINISHED) {
+ false
+ } else {
+ gestureHandler.onMotionEvent(event)
+ }
+ }
+ )
+ ) {
+ content()
+ }
+}
+
+@Composable
+private fun GestureTutorialContent(
+ gestureState: GestureState,
+ onDoneButtonClicked: () -> Unit,
+ config: TutorialScreenConfig
+) {
+ val animatedColor by
+ animateColorAsState(
+ targetValue =
+ if (gestureState == FINISHED) config.colors.successBackground
+ else config.colors.background,
+ animationSpec = tween(durationMillis = 150, easing = LinearEasing),
+ label = "backgroundColor"
+ )
+ Column(
+ verticalArrangement = Arrangement.Center,
+ modifier =
+ Modifier.fillMaxSize()
+ .drawBehind { drawRect(animatedColor) }
+ .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
+ ) {
+ Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
+ TutorialDescription(
+ titleTextId =
+ if (gestureState == FINISHED) config.strings.titleSuccessResId
+ else config.strings.titleResId,
+ titleColor = config.colors.title,
+ bodyTextId =
+ if (gestureState == FINISHED) config.strings.bodySuccessResId
+ else config.strings.bodyResId,
+ modifier = Modifier.weight(1f)
+ )
+ Spacer(modifier = Modifier.width(76.dp))
+ TutorialAnimation(
+ gestureState,
+ config,
+ modifier = Modifier.weight(1f).padding(top = 8.dp)
+ )
+ }
+ DoneButton(onDoneButtonClicked = onDoneButtonClicked)
+ }
+}
+
+@Composable
+fun TutorialDescription(
+ @StringRes titleTextId: Int,
+ titleColor: Color,
+ @StringRes bodyTextId: Int,
+ modifier: Modifier = Modifier
+) {
+ Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
+ Text(
+ text = stringResource(id = titleTextId),
+ style = MaterialTheme.typography.displayLarge,
+ color = titleColor
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = stringResource(id = bodyTextId),
+ style = MaterialTheme.typography.bodyLarge,
+ color = Color.White
+ )
+ }
+}
+
+@Composable
+fun TutorialAnimation(
+ gestureState: GestureState,
+ config: TutorialScreenConfig,
+ modifier: Modifier = Modifier
+) {
+ Box(modifier = modifier.fillMaxWidth()) {
+ AnimatedContent(
+ targetState = gestureState,
+ transitionSpec = {
+ if (initialState == NOT_STARTED && targetState == IN_PROGRESS) {
+ val transitionDurationMillis = 150
+ 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
+ EnterTransition.None togetherWith ExitTransition.None
+ }
+ }
+ ) { gestureState ->
+ when (gestureState) {
+ NOT_STARTED ->
+ EducationAnimation(
+ config.animations.educationResId,
+ config.colors.animationColors
+ )
+ IN_PROGRESS ->
+ FrozenSuccessAnimation(
+ config.animations.successResId,
+ config.colors.animationColors
+ )
+ FINISHED ->
+ SuccessAnimation(config.animations.successResId, config.colors.animationColors)
+ }
+ }
+ }
+}
+
+@Composable
+private fun FrozenSuccessAnimation(
+ @RawRes successAnimationId: Int,
+ animationProperties: LottieDynamicProperties
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
+ LottieAnimation(
+ composition = composition,
+ progress = { 0f }, // animation should freeze on 1st frame
+ dynamicProperties = animationProperties,
+ )
+}
+
+@Composable
+private fun EducationAnimation(
+ @RawRes educationAnimationId: Int,
+ animationProperties: LottieDynamicProperties
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId))
+ val progress by
+ animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
+ LottieAnimation(
+ composition = composition,
+ progress = { progress },
+ dynamicProperties = animationProperties,
+ )
+}
+
+@Composable
+private fun SuccessAnimation(
+ @RawRes successAnimationId: Int,
+ animationProperties: LottieDynamicProperties
+) {
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
+ val progress by animateLottieCompositionAsState(composition, iterations = 1)
+ LottieAnimation(
+ composition = composition,
+ progress = { progress },
+ dynamicProperties = animationProperties,
+ )
+}
+
+@Composable
+fun rememberColorFilterProperty(
+ layerName: String,
+ color: Color
+): LottieDynamicProperty<ColorFilter> {
+ return rememberLottieDynamicProperty(
+ LottieProperty.COLOR_FILTER,
+ value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP),
+ // "**" below means match zero or more layers, so ** layerName ** means find layer with that
+ // name at any depth
+ keyPath = arrayOf("**", layerName, "**")
+ )
+}
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/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt
new file mode 100644
index 0000000..d76ceb9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialScreenConfig.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.annotation.RawRes
+import androidx.annotation.StringRes
+import androidx.compose.ui.graphics.Color
+import com.airbnb.lottie.compose.LottieDynamicProperties
+
+data class TutorialScreenConfig(
+ val colors: Colors,
+ val strings: Strings,
+ val animations: Animations
+) {
+
+ data class Colors(
+ val background: Color,
+ val successBackground: Color,
+ val title: Color,
+ val animationColors: LottieDynamicProperties
+ )
+
+ data class Strings(
+ @StringRes val titleResId: Int,
+ @StringRes val bodyResId: Int,
+ @StringRes val titleSuccessResId: Int,
+ @StringRes val bodySuccessResId: Int,
+ )
+
+ data class Animations(
+ @RawRes val educationResId: Int,
+ @RawRes val successResId: Int,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index ed3355e..e3666ce 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -16,38 +16,26 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
-import android.view.MotionEvent
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import kotlin.math.abs
+/** Monitors for touchpad back gesture, that is three fingers swiping left or right */
class BackGestureMonitor(
override val gestureDistanceThresholdPx: Int,
override val gestureStateChangedCallback: (GestureState) -> Unit
-) : TouchpadGestureMonitor {
-
- private var xStart = 0f
-
- override fun processTouchpadEvent(event: MotionEvent) {
- val action = event.actionMasked
- when (action) {
- MotionEvent.ACTION_DOWN -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- xStart = event.x
- gestureStateChangedCallback(IN_PROGRESS)
+) :
+ TouchpadGestureMonitor by ThreeFingerGestureMonitor(
+ gestureDistanceThresholdPx = gestureDistanceThresholdPx,
+ gestureStateChangedCallback = gestureStateChangedCallback,
+ donePredicate =
+ object : GestureDonePredicate {
+ override fun wasGestureDone(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float
+ ): Boolean {
+ val distance = abs(endX - startX)
+ return distance >= gestureDistanceThresholdPx
}
}
- MotionEvent.ACTION_UP -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- val distance = abs(event.x - xStart)
- if (distance >= gestureDistanceThresholdPx) {
- gestureStateChangedCallback(FINISHED)
- } else {
- gestureStateChangedCallback(NOT_STARTED)
- }
- }
- }
- }
- }
-}
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
new file mode 100644
index 0000000..a410f99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.gesture
+
+/** Monitors for touchpad home gesture, that is three fingers swiping up */
+class HomeGestureMonitor(
+ override val gestureDistanceThresholdPx: Int,
+ override val gestureStateChangedCallback: (GestureState) -> Unit
+) :
+ TouchpadGestureMonitor by ThreeFingerGestureMonitor(
+ gestureDistanceThresholdPx = gestureDistanceThresholdPx,
+ gestureStateChangedCallback = gestureStateChangedCallback,
+ donePredicate =
+ object : GestureDonePredicate {
+ override fun wasGestureDone(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float
+ ): Boolean {
+ val distance = startY - endY
+ return distance >= gestureDistanceThresholdPx
+ }
+ }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt
new file mode 100644
index 0000000..377977c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/ThreeFingerGestureMonitor.kt
@@ -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.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+
+interface GestureDonePredicate {
+ /**
+ * Should return if gesture was finished. The only events this predicate receives are ACTION_UP.
+ */
+ fun wasGestureDone(startX: Float, startY: Float, endX: Float, endY: Float): Boolean
+}
+
+/** Common implementation for all three-finger gesture monitors */
+class ThreeFingerGestureMonitor(
+ override val gestureDistanceThresholdPx: Int,
+ override val gestureStateChangedCallback: (GestureState) -> Unit,
+ private val donePredicate: GestureDonePredicate
+) : TouchpadGestureMonitor {
+
+ private var xStart = 0f
+ private var yStart = 0f
+
+ override fun processTouchpadEvent(event: MotionEvent) {
+ val action = event.actionMasked
+ when (action) {
+ MotionEvent.ACTION_DOWN -> {
+ if (isThreeFingerTouchpadSwipe(event)) {
+ xStart = event.x
+ yStart = event.y
+ gestureStateChangedCallback(GestureState.IN_PROGRESS)
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (isThreeFingerTouchpadSwipe(event)) {
+ if (donePredicate.wasGestureDone(xStart, yStart, event.x, event.y)) {
+ gestureStateChangedCallback(GestureState.FINISHED)
+ } else {
+ gestureStateChangedCallback(GestureState.NOT_STARTED)
+ }
+ }
+ }
+ }
+ }
+}
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/sensors/ProximityCheck.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java
index c06a3a1..5b9a6c5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximityCheck.java
@@ -86,12 +86,15 @@
}
private void onProximityEvent(ThresholdSensorEvent proximityEvent) {
- mCallbacks.forEach(
+ // Move the callbacks to a local to avoid ConcurrentModificationException
+ List<Consumer<Boolean>> oldCallbacks = mCallbacks;
+ mCallbacks = new ArrayList<>();
+ // Unregister from the ProximitySensor to ensure a re-entrant check will re-register
+ unregister();
+ // Notify the callbacks
+ oldCallbacks.forEach(
booleanConsumer ->
booleanConsumer.accept(
proximityEvent == null ? null : proximityEvent.getBelow()));
- mCallbacks.clear();
- unregister();
- mRegistered.set(false);
}
}
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/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index d757e33..36468144 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -19,6 +19,7 @@
import android.annotation.UserIdInt
import android.database.ContentObserver
+import com.android.systemui.Flags
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -39,9 +40,21 @@
}
}
- names.forEach { name -> registerContentObserverForUserSync(name, observer, userId) }
+ names.forEach { name ->
+ if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+ registerContentObserverForUser(name, observer, userId)
+ } else {
+ registerContentObserverForUserSync(name, observer, userId)
+ }
+ }
- awaitClose { unregisterContentObserverSync(observer) }
+ awaitClose {
+ if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+ unregisterContentObserverAsync(observer)
+ } else {
+ unregisterContentObserverSync(observer)
+ }
+ }
}
}
@@ -57,9 +70,21 @@
}
}
- names.forEach { name -> registerContentObserverSync(name, observer) }
+ names.forEach { name ->
+ if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+ registerContentObserver(name, observer)
+ } else {
+ registerContentObserverSync(name, observer)
+ }
+ }
- awaitClose { unregisterContentObserverSync(observer) }
+ awaitClose {
+ if (Flags.settingsExtRegisterContentObserverOnBgThread()) {
+ unregisterContentObserverAsync(observer)
+ } else {
+ unregisterContentObserverSync(observer)
+ }
+ }
}
}
}
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 b8b93c9..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
@@ -23,7 +23,6 @@
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager.NameNotFoundException
-import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
@@ -99,9 +98,10 @@
private const val REQUEST_ID = 4L
private const val CHALLENGE = 2L
private const val DELAY = 1000L
-private const val OP_PACKAGE_NAME = "biometric.testapp"
+private const val OP_PACKAGE_NAME_WITH_APP_LOGO = "biometric.testapp"
private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon"
private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found"
+private const val OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO = "should.use.activiy.logo"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -111,19 +111,21 @@
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var authController: AuthController
- @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
- @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
+ @Mock private lateinit var applicationInfoWithIconAndDescription: ApplicationInfo
+ @Mock private lateinit var applicationInfoNoIconOrDescription: ApplicationInfo
@Mock private lateinit var activityInfo: ActivityInfo
@Mock private lateinit var runningTaskInfo: RunningTaskInfo
- private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
- private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
+ private val defaultLogoIconFromAppInfo = context.getDrawable(R.drawable.ic_android)
+ private val defaultLogoIconFromActivityInfo = context.getDrawable(R.drawable.ic_add)
private val logoResFromApp = R.drawable.ic_cake
private val logoDrawableFromAppRes = context.getDrawable(logoResFromApp)
private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
- private val defaultLogoDescription = "Test Android App"
+ private val defaultLogoDescriptionFromAppInfo = "Test Android App"
+ private val defaultLogoDescriptionFromActivityInfo = "Test Coke App"
private val logoDescriptionFromApp = "Test Cake App"
private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
+
/** Prompt panel size padding */
private val smallHorizontalGuidelinePadding =
context.resources.getDimensionPixelSize(
@@ -168,19 +170,23 @@
@Before
fun setup() {
-
// Set up default logo info and app customized info
whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
- .thenReturn(applicationInfoNoIcon)
- whenever(kosmos.packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
- .thenReturn(applicationInfoWithIcon)
+ .thenReturn(applicationInfoNoIconOrDescription)
whenever(
kosmos.packageManager.getApplicationInfo(
- eq(packageNameForLogoWithOverrides),
+ eq(OP_PACKAGE_NAME_WITH_APP_LOGO),
anyInt()
)
)
- .thenReturn(applicationInfoWithIcon)
+ .thenReturn(applicationInfoWithIconAndDescription)
+ whenever(
+ kosmos.packageManager.getApplicationInfo(
+ eq(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO),
+ anyInt()
+ )
+ )
+ .thenReturn(applicationInfoWithIconAndDescription)
whenever(
kosmos.packageManager.getApplicationInfo(
eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND),
@@ -190,19 +196,27 @@
.thenThrow(NameNotFoundException())
whenever(kosmos.packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo)
- whenever(kosmos.iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides)
- whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIcon))
- .thenReturn(defaultLogoIcon)
- whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIcon))
- .thenReturn(defaultLogoDescription)
+ whenever(kosmos.iconProvider.getIcon(activityInfo))
+ .thenReturn(defaultLogoIconFromActivityInfo)
+ whenever(activityInfo.loadLabel(kosmos.packageManager))
+ .thenReturn(defaultLogoDescriptionFromActivityInfo)
+
+ whenever(kosmos.packageManager.getApplicationIcon(applicationInfoWithIconAndDescription))
+ .thenReturn(defaultLogoIconFromAppInfo)
+ whenever(kosmos.packageManager.getApplicationLabel(applicationInfoWithIconAndDescription))
+ .thenReturn(defaultLogoDescriptionFromAppInfo)
+ whenever(kosmos.packageManager.getApplicationIcon(applicationInfoNoIconOrDescription))
+ .thenReturn(null)
+ whenever(kosmos.packageManager.getApplicationLabel(applicationInfoNoIconOrDescription))
+ .thenReturn("")
whenever(kosmos.packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) }
whenever(kosmos.packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) }
context.setMockPackageManager(kosmos.packageManager)
overrideResource(logoResFromApp, logoDrawableFromAppRes)
overrideResource(
- R.array.biometric_dialog_package_names_for_logo_with_overrides,
- arrayOf(packageNameForLogoWithOverrides)
+ R.array.config_useActivityLogoForBiometricPrompt,
+ arrayOf(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO)
)
overrideResource(R.dimen.biometric_dialog_fingerprint_icon_width, mockFingerprintIconWidth)
@@ -232,15 +246,6 @@
kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
testCase.fingerprint?.isAnySidefpsType.let {
kosmos.displayStateRepository.setIsInRearDisplayMode(testCase.isInRearDisplayMode)
- if (testCase.isDeviceFolded) {
- kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
- getFoldedConfiguration()
- )
- } else {
- kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
- getUnfoldedConfiguration()
- )
- }
}
promptContentView =
@@ -265,12 +270,8 @@
val authenticated by collectLastValue(kosmos.promptViewModel.isAuthenticated)
val modalities by collectLastValue(kosmos.promptViewModel.modalities)
val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
val shouldAnimateIconView by
collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
val iconContentDescriptionId by
collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
val message by collectLastValue(kosmos.promptViewModel.message)
@@ -293,9 +294,7 @@
if ((testCase.isCoex && !forceExplicitFlow) || testCase.isFaceOnly) {
// Face-only or implicit co-ex auth
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_idle_static)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(shouldAnimateIconView).isEqualTo(false)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
if (forceExplicitFlow) {
@@ -311,29 +310,22 @@
if ((testCase.isCoex && !forceExplicitFlow) || testCase.isFaceOnly) {
// Face-only or implicit co-ex auth
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_authenticating)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
} else if ((testCase.isCoex && forceExplicitFlow) || testCase.isFingerprintOnly) {
// Fingerprint-only or explicit co-ex auth
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
- assertThat(iconOverlayAsset)
- .isEqualTo(R.raw.biometricprompt_fingerprint_to_error_landscape)
+ assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintAuthenticating())
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
} else {
assertThat(iconAsset)
.isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.fingerprint_dialog_touch_sensor)
assertThat(shouldAnimateIconView).isEqualTo(false)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
}
@@ -347,14 +339,10 @@
@Test
fun start_authenticating_show_and_clear_error() = runGenericTest {
val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
val iconContentDescriptionId by
collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
val shouldAnimateIconView by
collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
val message by collectLastValue(kosmos.promptViewModel.message)
var forceExplicitFlow =
@@ -373,7 +361,6 @@
forceExplicitFlow = true
// Usually done by binder
kosmos.promptViewModel.iconViewModel.setPreviousIconWasError(true)
- kosmos.promptViewModel.iconViewModel.setPreviousIconOverlayWasError(true)
}
assertThat(message?.isError).isEqualTo(true)
@@ -383,70 +370,47 @@
if (testCase.isFaceOnly) {
// Face-only auth
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_error)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
// Clear error, go to idle
errorJob.join()
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_error_to_idle)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
} else if ((testCase.isCoex && forceExplicitFlow) || testCase.isFingerprintOnly) {
// Fingerprint-only or explicit co-ex auth
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
- assertThat(iconOverlayAsset)
- .isEqualTo(R.raw.biometricprompt_fingerprint_to_error_landscape)
+ assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToError())
assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(true)
} else {
assertThat(iconAsset)
.isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
// Clear error, restart authenticating
errorJob.join()
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
- assertThat(iconOverlayAsset)
- .isEqualTo(R.raw.biometricprompt_symbol_error_to_fingerprint_landscape)
+ assertThat(iconAsset).isEqualTo(getSfpsAsset_errorToFingerprint())
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
- assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
} else {
assertThat(iconAsset)
.isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.fingerprint_dialog_touch_sensor)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
}
}
- private fun getSfpsBaseIconAsset(): Int {
- return if (testCase.isInRearDisplayMode) {
- R.raw.biometricprompt_rear_landscape_base
- } else if (testCase.isDeviceFolded) {
- R.raw.biometricprompt_folded_base_default
- } else {
- R.raw.biometricprompt_landscape_base
- }
- }
-
@Test
fun shows_error_to_unlock_or_success() {
// Face-only auth does not use error -> unlock or error -> success assets
@@ -476,9 +440,30 @@
dismissAfterDelay = DELAY
)
- // TODO(b/350121748): SFPS test cases to be added after SFPS assets update
- if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- // Non-SFPS (UDFPS / rear-FPS) test cases
+ // SFPS test cases
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ // Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint
+ if (testCase.authenticatedByFingerprint) {
+ assertThat(iconAsset).isEqualTo(R.raw.biometricprompt_sfps_error_to_success)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ } else { // Covers co-ex, authenticated by face
+ assertThat(iconAsset).isEqualTo(R.raw.biometricprompt_sfps_error_to_unlock)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+
+ // Confirm authentication
+ kosmos.promptViewModel.confirmAuthenticated()
+
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.biometricprompt_sfps_unlock_to_success)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ }
+ } else { // Non-SFPS (UDFPS / rear-FPS) test cases
// Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint
if (testCase.authenticatedByFingerprint) {
assertThat(iconAsset)
@@ -514,51 +499,41 @@
if (!testCase.confirmationRequested) {
runGenericTest {
val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
val iconContentDescriptionId by
collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
val shouldAnimateIconView by
collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
+ val message by collectLastValue(kosmos.promptViewModel.message)
verifyIconSize()
kosmos.promptViewModel.showAuthenticated(
modality = testCase.authenticatedModality,
- dismissAfterDelay = DELAY
+ dismissAfterDelay = DELAY,
+ "TEST"
)
if (testCase.isFingerprintOnly) {
// Fingerprint icon asset assertions
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
- assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset())
- assertThat(iconOverlayAsset)
- .isEqualTo(
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- )
+ assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToSuccess())
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(true)
} else {
assertThat(iconAsset)
.isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.fingerprint_dialog_touch_sensor)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
} else if (testCase.isFaceOnly || testCase.isCoex) {
// Face icon asset assertions
// If co-ex, use implicit flow (explicit flow always requires confirmation)
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ assertThat(message).isEqualTo(PromptMessage.Empty)
}
}
}
@@ -569,14 +544,10 @@
if (testCase.authenticatedByFace && testCase.confirmationRequested) {
runGenericTest {
val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
val iconContentDescriptionId by
collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
val shouldAnimateIconView by
collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
verifyIconSize(forceExplicitFlow = forceExplicitFlow)
@@ -588,21 +559,21 @@
if (testCase.isFaceOnly) {
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
} else if (testCase.isCoex) { // explicit flow, confirmation requested
- // TODO: Update when SFPS co-ex is implemented
- if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToUnlock())
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ } else {
assertThat(iconAsset)
.isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_confirm)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
}
}
@@ -614,14 +585,10 @@
if (testCase.authenticatedByFace && testCase.confirmationRequested) {
runGenericTest {
val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
- val iconOverlayAsset by
- collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset)
val iconContentDescriptionId by
collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId)
val shouldAnimateIconView by
collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView)
- val shouldAnimateIconOverlay by
- collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay)
val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested
verifyIconSize(forceExplicitFlow = forceExplicitFlow)
@@ -634,32 +601,66 @@
if (testCase.isFaceOnly) {
assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
// explicit flow because confirmation requested
if (testCase.isCoex) {
- // TODO: Update when SFPS co-ex is implemented
- if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.biometricprompt_sfps_unlock_to_success)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ } else {
assertThat(iconAsset)
.isEqualTo(
R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
)
- assertThat(iconOverlayAsset).isEqualTo(-1)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.fingerprint_dialog_touch_sensor)
assertThat(shouldAnimateIconView).isEqualTo(true)
- assertThat(shouldAnimateIconOverlay).isEqualTo(false)
}
}
}
}
}
+ private fun getSfpsAsset_fingerprintAuthenticating(): Int =
+ if (testCase.isInRearDisplayMode) {
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_authenticating
+ } else {
+ R.raw.biometricprompt_sfps_fingerprint_authenticating
+ }
+
+ private fun getSfpsAsset_fingerprintToError(): Int =
+ if (testCase.isInRearDisplayMode) {
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_error
+ } else {
+ R.raw.biometricprompt_sfps_fingerprint_to_error
+ }
+
+ private fun getSfpsAsset_fingerprintToUnlock(): Int =
+ if (testCase.isInRearDisplayMode) {
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_unlock
+ } else {
+ R.raw.biometricprompt_sfps_fingerprint_to_unlock
+ }
+
+ private fun getSfpsAsset_errorToFingerprint(): Int =
+ if (testCase.isInRearDisplayMode) {
+ R.raw.biometricprompt_sfps_rear_display_error_to_fingerprint
+ } else {
+ R.raw.biometricprompt_sfps_error_to_fingerprint
+ }
+
+ private fun getSfpsAsset_fingerprintToSuccess(): Int =
+ if (testCase.isInRearDisplayMode) {
+ R.raw.biometricprompt_sfps_rear_display_fingerprint_to_success
+ } else {
+ R.raw.biometricprompt_sfps_fingerprint_to_success
+ }
+
@Test
fun shows_authenticated_with_no_errors() = runGenericTest {
// this case can't happen until fingerprint is started
@@ -762,36 +763,19 @@
.isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
}
+ // biometricprompt_sfps_fingerprint_authenticating reused across rotations
+ // Other SFPS assets change across rotations, testing authenticated asset
@Test
- fun sfpsIconUpdates_onFoldConfigurationChanged() {
- if (
- testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON &&
- !testCase.isInRearDisplayMode
- ) {
- runGenericTest {
- val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
-
- kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
- getFoldedConfiguration()
- )
- val foldedIcon = currentIcon
-
- kosmos.promptViewModel.iconViewModel.onConfigurationChanged(
- getUnfoldedConfiguration()
- )
- val unfoldedIcon = currentIcon
-
- assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
- }
- }
- }
-
- @Test
- fun sfpsIconUpdates_onRotation() {
+ fun sfpsAuthenticatedIconUpdates_onRotation() {
if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
runGenericTest {
val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset)
+ kosmos.promptViewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
val iconRotation0 = currentIcon
@@ -804,9 +788,12 @@
kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
val iconRotation270 = currentIcon
- assertThat(iconRotation0).isEqualTo(iconRotation180)
assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation180)
assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+ assertThat(iconRotation90).isNotEqualTo(iconRotation180)
+ assertThat(iconRotation90).isNotEqualTo(iconRotation270)
+ assertThat(iconRotation180).isNotEqualTo(iconRotation270)
}
}
}
@@ -1437,36 +1424,41 @@
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_nullIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat(logo).isNull()
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isNull()
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
- fun logo_defaultWithOverrides() =
- runGenericTest(packageName = packageNameForLogoWithOverrides) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
+ fun logo_defaultFromActivityInfo() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
- // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
- // applicationInfoWithIcon with defaultLogoIcon,
- // 2. iconProvider.getIcon() is set to return defaultLogoIconForGMSCore
- // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
- assertThat(logo).isEqualTo(defaultLogoIconWithOverrides)
+ // 1. PM.getApplicationInfo(OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) is set to return
+ // applicationInfoWithIconAndDescription with "defaultLogoIconFromAppInfo",
+ // 2. iconProvider.getIcon(activityInfo) is set to return
+ // "defaultLogoIconFromActivityInfo"
+ // For the apps with OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO, 2 should be called instead of 1
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromActivityInfo)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_defaultIsNull() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat(logo).isNull()
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isNull()
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_default() = runGenericTest {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat(logo).isEqualTo(defaultLogoIcon)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconFromAppInfo)
}
@Test
@@ -1474,47 +1466,60 @@
fun logo_resSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat((logo as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo).isNotNull()
+ assertThat((logoInfo!!.first as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_bitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
- val logo by collectLastValue(kosmos.promptViewModel.logo)
- assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat((logoInfo!!.first as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_emptyIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo("")
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo("")
+ }
+
+ @Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
+ fun logoDescription_defaultFromActivityInfo() =
+ runGenericTest(packageName = OP_PACKAGE_NAME_WITH_ACTIVITY_LOGO) {
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
+ // applicationInfoWithIconAndDescription with defaultLogoDescription,
+ // 2. activityInfo.loadLabel() is set to return defaultLogoDescriptionWithOverrides
+ // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1
+ assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromActivityInfo)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_defaultIsEmpty() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo("")
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo("")
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_default() = runGenericTest {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo(defaultLogoDescription)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
}
@Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_setByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
- val logoDescription by collectLastValue(kosmos.promptViewModel.logoDescription)
- assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
+ val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
+ assertThat(logoInfo!!.second).isEqualTo(logoDescriptionFromApp)
}
@Test
@@ -1689,7 +1694,7 @@
logoRes: Int = 0,
logoBitmap: Bitmap? = null,
logoDescription: String? = null,
- packageName: String = OP_PACKAGE_NAME,
+ packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
block: suspend TestScope.() -> Unit,
) {
val topActivity = ComponentName(packageName, "test app")
@@ -1786,18 +1791,6 @@
.first(),
authenticatedModality = BiometricModality.Fingerprint,
isInRearDisplayMode = false,
- isDeviceFolded = false
- ),
- TestCase(
- fingerprint =
- fingerprintSensorPropertiesInternal(
- strong = true,
- sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
- )
- .first(),
- authenticatedModality = BiometricModality.Fingerprint,
- isInRearDisplayMode = false,
- isDeviceFolded = true
),
TestCase(
fingerprint =
@@ -1808,7 +1801,6 @@
.first(),
authenticatedModality = BiometricModality.Fingerprint,
isInRearDisplayMode = true,
- isDeviceFolded = false
),
TestCase(
fingerprint =
@@ -1883,7 +1875,6 @@
val fingerprint: FingerprintSensorPropertiesInternal? = null,
val face: FaceSensorPropertiesInternal? = null,
val isInRearDisplayMode: Boolean = false,
- val isDeviceFolded: Boolean = false,
val authenticatedModality: BiometricModality,
val confirmationRequested: Boolean = false,
) {
@@ -1899,9 +1890,8 @@
face != null -> "face only"
else -> "?"
}
- return "[$modality, isInRearDisplayMode: $isInRearDisplayMode, " +
- "isDeviceFolded: $isDeviceFolded, by: $authenticatedModality, " +
- "confirm: $confirmationRequested]"
+ return "[$modality, isInRearDisplayMode: $isInRearDisplayMode, by: " +
+ "$authenticatedModality, confirm: $confirmationRequested]"
}
fun expectConfirmation(atLeastOneFailure: Boolean): Boolean =
@@ -1948,7 +1938,7 @@
logoResFromApp: Int = 0,
logoBitmapFromApp: Bitmap? = null,
logoDescriptionFromApp: String? = null,
- packageName: String = OP_PACKAGE_NAME,
+ packageName: String = OP_PACKAGE_NAME_WITH_APP_LOGO,
) {
val info =
PromptInfo().apply {
@@ -1981,21 +1971,3 @@
withArgCaptor<AuthController.Callback> {
Mockito.verify(this@captureCallback).addCallback(capture())
}
-
-/** Get folded device configuration */
-fun getFoldedConfiguration(): Configuration {
- val testConfig = Configuration()
- val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
- testConfig.smallestScreenWidthDp = folded
- return testConfig
-}
-
-/** Get unfolded device configuration */
-fun getUnfoldedConfiguration(): Configuration {
- val testConfig = Configuration()
- val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
- testConfig.smallestScreenWidthDp = unfolded
- return testConfig
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
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/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
index 0489d81..945f86c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt
@@ -89,10 +89,7 @@
verify(taskView).startActivity(any(), any(), capture(optionsCaptor), any())
assertThat(optionsCaptor.value.pendingIntentBackgroundActivityStartMode)
- .isAnyOf(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
- assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission)
- .isTrue()
+ .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
}
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/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 48a5df9..2af4d87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -45,6 +45,7 @@
import java.math.RoundingMode
import java.util.UUID
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -332,10 +333,11 @@
}
@Test
- fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() {
- underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
- assertThat(wtfHandler.failed).isTrue()
- }
+ fun attemptTomanuallyUpdateTransitionWithInvalidUUIDthrowsException() =
+ testScope.runTest {
+ underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
+ assertThat(wtfHandler.failed).isTrue()
+ }
@Test
fun attemptToManuallyUpdateTransitionAfterFINISHEDstateThrowsException() =
@@ -414,6 +416,64 @@
)
}
+ @Test
+ fun simulateRaceConditionIsProcessedInOrderUsingUpdateTransition() =
+ testScope.runTest {
+ val ktr = KeyguardTransitionRepositoryImpl(kosmos.testDispatcher)
+ val steps by collectValues(ktr.transitions.dropWhile { step -> step.from == OFF })
+
+ // Begin a manual transition
+ val info1 = TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
+ launch {
+ ktr.forceDelayForRaceConditionTest = false
+ val uuid = ktr.startTransition(info1)
+
+ // Pause here to allow another transition to start
+ delay(20)
+
+ // Attempt to send an update, which should fail
+ ktr.updateTransition(uuid!!, 0.5f, TransitionState.RUNNING)
+ }
+
+ // Now start another transition, which should acquire the preempt the first
+ val info2 = TransitionInfo(OWNER_NAME, LOCKSCREEN, OCCLUDED, animator = null)
+ launch {
+ delay(10)
+ ktr.forceDelayForRaceConditionTest = true
+ ktr.startTransition(info2)
+ }
+
+ runCurrent()
+
+ // Manual transition has started
+ assertThat(steps[0])
+ .isEqualTo(
+ TransitionStep(info1.from, info1.to, 0f, TransitionState.STARTED, OWNER_NAME)
+ )
+
+ // The second transition has requested to start, and grabbed the mutex. But it is
+ // delayed
+ advanceTimeBy(15L)
+
+ // Advancing another 10ms should now trigger the first transition to request an update,
+ // which should not happen as the second transition has the mutex
+ advanceTimeBy(10L)
+
+ // Finally, advance past the delay in the second transition so it can run
+ advanceTimeBy(50L)
+
+ assertThat(steps[1])
+ .isEqualTo(
+ TransitionStep(info1.from, info1.to, 0f, TransitionState.CANCELED, OWNER_NAME)
+ )
+ assertThat(steps[2])
+ .isEqualTo(
+ TransitionStep(info2.from, info2.to, 0f, TransitionState.STARTED, OWNER_NAME)
+ )
+
+ assertThat(steps.size).isEqualTo(3)
+ }
+
private fun listWithStep(
step: BigDecimal,
start: BigDecimal = BigDecimal.ZERO,
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/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
index f884b87..04ef1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
@@ -29,23 +29,26 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertEquals
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: AlertDialog
private val appName = "Test App"
- private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
- private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+ private val resIdSingleApp =
+ R.string.media_projection_entry_app_permission_dialog_option_text_single_app
+ private val resIdFullScreen =
+ R.string.media_projection_entry_app_permission_dialog_option_text_entire_screen
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
@@ -116,38 +119,45 @@
}
@Test
- fun showDialog_disableSingleApp_hasCastingCapabilities() {
- setUpAndShowDialog(
- mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- hasCastingCapabilities = true
- )
+ fun startButtonText_entireScreenSelected() {
+ setUpAndShowDialog()
+ onSpinnerItemSelected(ENTIRE_SCREEN)
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
- val secondOptionWarningText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text2)
- ?.text
+ val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text
- // check that the first option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+ assertThat(startButtonText)
+ .isEqualTo(
+ context.getString(
+ R.string.media_projection_entry_app_permission_dialog_continue_entire_screen
+ )
+ )
+ }
- // check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
+ @Test
+ fun startButtonText_singleAppSelected() {
+ setUpAndShowDialog()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text
+
+ assertThat(startButtonText)
+ .isEqualTo(
+ context.getString(
+ R.string.media_projection_entry_generic_permission_dialog_continue_single_app
+ )
+ )
}
private fun setUpAndShowDialog(
mediaProjectionConfig: MediaProjectionConfig? = null,
overrideDisableSingleAppOption: Boolean = false,
- hasCastingCapabilities: Boolean = false,
) {
val delegate =
- MediaProjectionPermissionDialogDelegate(
+ ShareToAppPermissionDialogDelegate(
context,
mediaProjectionConfig,
onStartRecordingClicked = {},
onCancelClicked = {},
- hasCastingCapabilities,
appName,
overrideDisableSingleAppOption,
hostUid = 12345,
@@ -165,4 +175,10 @@
delegate.onCreate(dialog, savedInstanceState = null)
dialog.show()
}
+
+ private fun onSpinnerItemSelected(position: Int) {
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ checkNotNull(spinner.onItemSelectedListener)
+ .onItemSelected(spinner, mock(), position, /* id= */ 0)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
similarity index 75%
copy from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
index f884b87..6495b66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
@@ -29,23 +29,26 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertEquals
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: AlertDialog
private val appName = "Test App"
- private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
- private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+ private val resIdSingleApp =
+ R.string.media_projection_entry_cast_permission_dialog_option_text_single_app
+ private val resIdFullScreen =
+ R.string.media_projection_entry_cast_permission_dialog_option_text_entire_screen
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
@@ -98,7 +101,7 @@
fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
setUpAndShowDialog(
mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- overrideDisableSingleAppOption = true
+ overrideDisableSingleAppOption = true,
)
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
@@ -116,42 +119,49 @@
}
@Test
- fun showDialog_disableSingleApp_hasCastingCapabilities() {
- setUpAndShowDialog(
- mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- hasCastingCapabilities = true
- )
+ fun startButtonText_entireScreenSelected() {
+ setUpAndShowDialog()
+ onSpinnerItemSelected(ENTIRE_SCREEN)
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
- val secondOptionWarningText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text2)
- ?.text
+ val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text
- // check that the first option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+ assertThat(startButtonText)
+ .isEqualTo(
+ context.getString(
+ R.string.media_projection_entry_cast_permission_dialog_continue_entire_screen
+ )
+ )
+ }
- // check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText)
+ @Test
+ fun startButtonText_singleAppSelected() {
+ setUpAndShowDialog()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text
+
+ assertThat(startButtonText)
+ .isEqualTo(
+ context.getString(
+ R.string.media_projection_entry_generic_permission_dialog_continue_single_app
+ )
+ )
}
private fun setUpAndShowDialog(
mediaProjectionConfig: MediaProjectionConfig? = null,
overrideDisableSingleAppOption: Boolean = false,
- hasCastingCapabilities: Boolean = false,
) {
val delegate =
- MediaProjectionPermissionDialogDelegate(
+ SystemCastPermissionDialogDelegate(
context,
mediaProjectionConfig,
onStartRecordingClicked = {},
onCancelClicked = {},
- hasCastingCapabilities,
appName,
overrideDisableSingleAppOption,
hostUid = 12345,
- mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+ mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>(),
)
dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
@@ -159,10 +169,16 @@
SystemUIDialog.setDialogSize(dialog)
dialog.window?.addSystemFlags(
- WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
)
delegate.onCreate(dialog, savedInstanceState = null)
dialog.show()
}
+
+ private fun onSpinnerItemSelected(position: Int) {
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ checkNotNull(spinner.onItemSelectedListener)
+ .onItemSelected(spinner, mock(), position, /* id= */ 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/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 4f4f0d9..2f41ac17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -45,6 +45,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCapture;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -54,6 +55,8 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,6 +97,7 @@
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private CommandQueue mCommandQueue;
@Mock private IVrManager mVrManager;
+ @Mock private Lazy<ViewCapture> mLazyViewCapture;
@Before
public void setup() {
@@ -705,7 +709,8 @@
mEnhancedEstimates,
mWakefulnessLifecycle,
mPowerManager,
- mUserTracker);
+ mUserTracker,
+ mLazyViewCapture);
mPowerUI.mThermalService = mThermalServiceMock;
}
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/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index e1c3911..b02cccc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduStatsInteractor
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
@@ -121,6 +122,9 @@
Optional<UnfoldTransitionProgressForwarder>
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var keyboardTouchpadEduStatsInteractor: KeyboardTouchpadEduStatsInteractor
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -289,7 +293,8 @@
assistUtils,
dumpManager,
unfoldTransitionProgressForwarder,
- broadcastDispatcher
+ broadcastDispatcher,
+ keyboardTouchpadEduStatsInteractor
)
}
}
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/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
index cd8a740..8f41caf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
@@ -39,7 +40,7 @@
kosmos.testScope.runTest {
val latest by collectLastValue(underTest.ongoingCallState)
- val inCall = OngoingCallModel.InCall(startTimeMs = 1000, intent = null)
+ val inCall = inCallModel(startTimeMs = 1000)
repo.setOngoingCallState(inCall)
assertThat(latest).isEqualTo(inCall)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 1a6b420..ce79fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -17,8 +17,11 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
import android.app.PendingIntent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
@@ -26,11 +29,13 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -73,7 +78,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 0, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 0))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
}
@@ -83,7 +88,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = -2, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = -2))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
}
@@ -93,7 +98,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 345, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 345))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
}
@@ -106,7 +111,7 @@
kosmos.fakeSystemClock.setCurrentTimeMillis(3000)
kosmos.fakeSystemClock.setElapsedRealtime(400_000)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
// The OngoingCallModel start time is relative to currentTimeMillis, so this call
// started 2000ms ago (1000 - 3000). The OngoingActivityChipModel start time needs to be
@@ -117,29 +122,97 @@
}
@Test
- fun chip_positiveStartTime_iconIsPhone() =
+ @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun chip_positiveStartTime_notifIconFlagOff_iconIsPhone() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
+ repo.setOngoingCallState(
+ inCallModel(startTimeMs = 1000, notificationIcon = mock<StatusBarIconView>())
+ )
- assertThat(((latest as OngoingActivityChipModel.Shown).icon as Icon.Resource).res)
- .isEqualTo(com.android.internal.R.drawable.ic_phone)
- assertThat((latest as OngoingActivityChipModel.Shown).icon!!.contentDescription)
- .isNotNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
+ assertThat(icon.contentDescription).isNotNull()
}
@Test
- fun chip_zeroStartTime_iconIsPhone() =
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun chip_positiveStartTime_notifIconFlagOn_iconIsNotifIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 0, intent = null))
+ val notifIcon = mock<StatusBarIconView>()
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = notifIcon))
- assertThat(((latest as OngoingActivityChipModel.Shown).icon as Icon.Resource).res)
- .isEqualTo(com.android.internal.R.drawable.ic_phone)
- assertThat((latest as OngoingActivityChipModel.Shown).icon!!.contentDescription)
- .isNotNull()
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
+ val actualIcon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.StatusBarView)
+ .impl
+ assertThat(actualIcon).isEqualTo(notifIcon)
+ }
+
+ @Test
+ @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ repo.setOngoingCallState(
+ inCallModel(startTimeMs = 0, notificationIcon = mock<StatusBarIconView>())
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
+ assertThat(icon.contentDescription).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun chip_zeroStartTime_notifIconFlagOn_iconIsNotifIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val notifIcon = mock<StatusBarIconView>()
+ repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon))
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
+ val actualIcon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.StatusBarView)
+ .impl
+ assertThat(actualIcon).isEqualTo(notifIcon)
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun chip_notifIconFlagOn_butNullNotifIcon_iconIsPhone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = null))
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.Basic::class.java)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
+ assertThat(icon.contentDescription).isNotNull()
}
@Test
@@ -147,7 +220,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
assertThat((latest as OngoingActivityChipModel.Shown).colors)
.isEqualTo(ColorsModel.Themed)
@@ -158,7 +231,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 0, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 0))
assertThat((latest as OngoingActivityChipModel.Shown).colors)
.isEqualTo(ColorsModel.Themed)
@@ -172,7 +245,7 @@
kosmos.fakeSystemClock.setElapsedRealtime(400_000)
// Start a call
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
.isEqualTo(398_000)
@@ -186,7 +259,7 @@
kosmos.fakeSystemClock.setElapsedRealtime(500_000)
// Start a new call, which started 1000ms ago
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 102_000, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 102_000))
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
assertThat((latest as OngoingActivityChipModel.Shown.Timer).startTimeMs)
.isEqualTo(499_000)
@@ -197,7 +270,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = null))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = null))
assertThat((latest as OngoingActivityChipModel.Shown).onClickListener).isNull()
}
@@ -208,7 +281,7 @@
val latest by collectLastValue(underTest.chip)
val intent = mock<PendingIntent>()
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 1000, intent = intent))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 1000, intent = intent))
val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
assertThat(clickListener).isNotNull()
@@ -223,7 +296,7 @@
val latest by collectLastValue(underTest.chip)
val intent = mock<PendingIntent>()
- repo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 0, intent = intent))
+ repo.setOngoingCallState(inCallModel(startTimeMs = 0, intent = intent))
val clickListener = (latest as OngoingActivityChipModel.Shown).onClickListener
assertThat(clickListener).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index 02764f8..a8d2c5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
@@ -125,8 +125,11 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
assertThat((icon.contentDescription as ContentDescription.Resource).res)
.isEqualTo(R.string.cast_screen_to_other_device_chip_accessibility_label)
}
@@ -141,8 +144,11 @@
MediaProjectionState.Projecting.EntireScreen(CAST_TO_OTHER_DEVICES_PACKAGE)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
assertThat((icon.contentDescription as ContentDescription.Resource).res)
.isEqualTo(R.string.cast_screen_to_other_device_chip_accessibility_label)
}
@@ -176,8 +182,11 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
// This content description is just generic "Casting", not "Casting screen"
assertThat((icon.contentDescription as ContentDescription.Resource).res)
.isEqualTo(R.string.accessibility_casting)
@@ -203,8 +212,11 @@
// Only the projection info will show a timer
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_cast_connected)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_cast_connected)
// MediaProjection == screen casting, so this content description reflects that we're
// using the MediaProjection information.
assertThat((icon.contentDescription as ContentDescription.Resource).res)
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 b4a37ee..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
@@ -148,8 +148,11 @@
screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenrecord)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
assertThat(icon.contentDescription).isNotNull()
}
@@ -228,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/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 2658679..a2ef599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -133,8 +133,11 @@
)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
assertThat(icon.contentDescription).isNotNull()
}
@@ -147,8 +150,11 @@
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
assertThat(icon.contentDescription).isNotNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index b9049e8..a724cfaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
+import androidx.annotation.DrawableRes
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
@@ -50,7 +51,7 @@
val newChip =
OngoingActivityChipModel.Shown.Timer(
- icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ icon = createIcon(R.drawable.ic_cake),
colors = ColorsModel.Themed,
startTimeMs = 100L,
onClickListener = null,
@@ -62,7 +63,7 @@
val newerChip =
OngoingActivityChipModel.Shown.IconOnly(
- icon = Icon.Resource(R.drawable.ic_hotspot, contentDescription = null),
+ icon = createIcon(R.drawable.ic_hotspot),
colors = ColorsModel.Themed,
onClickListener = null,
)
@@ -82,7 +83,7 @@
val shownChip =
OngoingActivityChipModel.Shown.Timer(
- icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ icon = createIcon(R.drawable.ic_cake),
colors = ColorsModel.Themed,
startTimeMs = 100L,
onClickListener = null,
@@ -122,7 +123,7 @@
val shownChip =
OngoingActivityChipModel.Shown.Timer(
- icon = Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+ icon = createIcon(R.drawable.ic_cake),
colors = ColorsModel.Themed,
startTimeMs = 100L,
onClickListener = null,
@@ -151,4 +152,7 @@
advanceTimeBy(2)
assertThat(latest).isEqualTo(shownChip)
}
+
+ private fun createIcon(@DrawableRes drawable: Int) =
+ OngoingActivityChipModel.ChipIcon.Basic(Icon.Resource(drawable, contentDescription = null))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index ee249f0..556ec6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -120,7 +121,7 @@
testScope.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- callRepo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34, intent = null))
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
val latest by collectLastValue(underTest.chip)
@@ -146,7 +147,7 @@
screenRecordState.value = ScreenRecordModel.DoingNothing
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- callRepo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34, intent = null))
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
val latest by collectLastValue(underTest.chip)
@@ -160,7 +161,7 @@
// MediaProjection covers both share-to-app and cast-to-other-device
mediaProjectionState.value = MediaProjectionState.NotProjecting
- callRepo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34, intent = null))
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
val latest by collectLastValue(underTest.chip)
@@ -171,7 +172,7 @@
fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
testScope.runTest {
// Start with just the lower priority call chip
- callRepo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34, intent = null))
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
mediaProjectionState.value = MediaProjectionState.NotProjecting
screenRecordState.value = ScreenRecordModel.DoingNothing
@@ -205,7 +206,7 @@
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- callRepo.setOngoingCallState(OngoingCallModel.InCall(startTimeMs = 34, intent = null))
+ callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
val latest by collectLastValue(underTest.chip)
@@ -335,21 +336,29 @@
fun assertIsScreenRecordChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_screenrecord)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_screenrecord)
}
fun assertIsShareToAppChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res).isEqualTo(R.drawable.ic_present_to_all)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
}
fun assertIsCallChip(latest: OngoingActivityChipModel?) {
assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- val icon = (latest as OngoingActivityChipModel.Shown).icon
- assertThat((icon as Icon.Resource).res)
- .isEqualTo(com.android.internal.R.drawable.ic_phone)
+ val icon =
+ (((latest as OngoingActivityChipModel.Shown).icon)
+ as OngoingActivityChipModel.ChipIcon.Basic)
+ .impl as Icon.Resource
+ assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index 6a5976e..48ae7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -396,9 +397,7 @@
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- ongoingCallRepository.setOngoingCallState(
- OngoingCallModel.InCall(startTimeMs = 34, intent = null)
- )
+ ongoingCallRepository.setOngoingCallState(inCallModel(startTimeMs = 34))
onSystemBarAttributesChanged(
requestedVisibleTypes = WindowInsets.Type.navigationBars(),
)
@@ -411,9 +410,8 @@
testScope.runTest {
val latest by collectLastValue(underTest.statusBarAppearance)
- ongoingCallRepository.setOngoingCallState(
- OngoingCallModel.InCall(startTimeMs = 789, intent = null)
- )
+ ongoingCallRepository.setOngoingCallState(inCallModel(startTimeMs = 789))
+
onSystemBarAttributesChanged(
requestedVisibleTypes = WindowInsets.Type.statusBars(),
appearance = APPEARANCE_OPAQUE_STATUS_BARS,
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/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index bfa816e..25138fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -30,9 +30,12 @@
import android.os.Bundle
import android.os.SystemClock
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -108,6 +111,28 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun testCreateIcons_chipNotifIconFlagDisabled_statusBarChipIconIsNull() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
+ entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
+
+ assertThat(entry?.icons?.statusBarChipIcon).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun testCreateIcons_chipNotifIconFlagEnabled_statusBarChipIconIsNull() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
+ entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
+
+ assertThat(entry?.icons?.statusBarChipIcon).isNotNull()
+ }
+
+ @Test
fun testCreateIcons_importantConversation_shortcutIcon() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
@@ -179,6 +204,7 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
fun testCreateIcons_sensitiveImportantConversation() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
@@ -187,11 +213,13 @@
entry?.let { iconManager.createIcons(it) }
testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
+ assertThat(entry?.icons?.statusBarChipIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
fun testUpdateIcons_sensitiveImportantConversation() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
@@ -202,6 +230,7 @@
entry?.let { iconManager.updateIcons(it) }
testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
+ assertThat(entry?.icons?.statusBarChipIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index f9509d2..d1b1f46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.notification.interruption
import android.Manifest.permission
+import android.app.Notification.CATEGORY_ALARM
+import android.app.Notification.CATEGORY_CAR_EMERGENCY
+import android.app.Notification.CATEGORY_CAR_WARNING
import android.app.Notification.CATEGORY_EVENT
import android.app.Notification.CATEGORY_REMINDER
import android.app.NotificationManager
@@ -256,6 +259,61 @@
}
@Test
+ fun testAvalancheFilter_duringAvalanche_allowCategoryAlarm() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
+ uiEventLogger, context, notificationManager)
+ ) {
+ ensurePeekState()
+ assertShouldHeadsUp(
+ buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ category = CATEGORY_ALARM
+ }
+ )
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowCategoryCarEmergency() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
+ uiEventLogger, context, notificationManager)
+ ) {
+ ensurePeekState()
+ assertShouldHeadsUp(
+ buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ category = CATEGORY_CAR_EMERGENCY
+
+ }
+ )
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowCategoryCarWarning() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(
+ AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
+ uiEventLogger, context, notificationManager)
+ ) {
+ ensurePeekState()
+ assertShouldHeadsUp(
+ buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ category = CATEGORY_CAR_WARNING
+ }
+ )
+ }
+ }
+
+ @Test
fun testAvalancheFilter_duringAvalanche_allowFsi() {
avalancheProvider.startTime = whenAgo(10)
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 54a26f7..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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
import android.app.Notification
+import android.app.Person
import android.content.Context
import android.os.AsyncTask
import android.os.Build
@@ -39,9 +40,11 @@
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
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
@@ -76,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
@@ -561,6 +564,40 @@
verify(row, times(0)).onNotificationUpdated()
}
+ // TODO b/356709333: Add screenshot tests for these views
+ @Test
+ fun testInflatePublicSingleLineView() {
+ row.publicLayout.removeAllViews()
+ inflateAndWait(false, notificationInflater, FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE, row)
+ Assert.assertNotNull(row.publicLayout.mSingleLineView)
+ Assert.assertTrue(row.publicLayout.mSingleLineView is HybridNotificationView)
+ }
+
+ @Test
+ fun testInflatePublicSingleLineConversationView() {
+ val testPerson = Person.Builder().setName("Person").build()
+ val messagingBuilder =
+ Notification.Builder(mContext, "no-id")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text")
+ .setStyle(Notification.MessagingStyle(testPerson))
+
+ val messagingRow = spy(testHelper.createRow(messagingBuilder.build()))
+ messagingRow.publicLayout.removeAllViews()
+ inflateAndWait(
+ false,
+ notificationInflater,
+ FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ messagingRow
+ )
+ Assert.assertNotNull(messagingRow.publicLayout.mSingleLineView)
+ // assert this is the conversation layout
+ Assert.assertTrue(
+ messagingRow.publicLayout.mSingleLineView is HybridConversationNotificationView
+ )
+ }
+
private class ExceptionHolder {
var exception: Exception? = null
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index d7fdce2..75376e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -620,8 +620,12 @@
.setUser(userHandle)
.setPostTime(System.currentTimeMillis())
.setChannel(channel)
+ .updateRanking(rankingBuilder -> rankingBuilder.setIsConversation(
+ notification.isStyle(Notification.MessagingStyle.class)
+ ))
.build();
+
return generateRow(entry, extraInflationFlags);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
deleted file mode 100644
index 53a1198..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
+++ /dev/null
@@ -1,117 +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.row
-
-import android.app.Notification
-import android.app.Person
-import android.platform.test.annotations.EnableFlags
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
-import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
-import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
-import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
-import com.android.systemui.util.mockito.mock
-import kotlin.test.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
-class SingleLineConversationViewBinderTest : SysuiTestCase() {
- private lateinit var notificationBuilder: Notification.Builder
- private lateinit var helper: NotificationTestHelper
-
- @Before
- fun setUp() {
- allowTestableLooperAsMainThread()
- helper = NotificationTestHelper(context, mDependency, TestableLooper.get(this))
- notificationBuilder = Notification.Builder(context, CHANNEL_ID)
- notificationBuilder
- .setSmallIcon(R.drawable.ic_corp_icon)
- .setContentTitle(CONTENT_TITLE)
- .setContentText(CONTENT_TEXT)
- }
-
- @Test
- @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
- fun bindGroupConversationSingleLineView() {
- // GIVEN a row with a group conversation notification
- val user =
- Person.Builder()
- // .setIcon(Icon.createWithResource(mContext,
- // R.drawable.ic_account_circle))
- .setName(USER_NAME)
- .build()
- val style =
- Notification.MessagingStyle(user)
- .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
- .addMessage(
- "How about lunch?",
- System.currentTimeMillis(),
- Person.Builder().setName("user2").build()
- )
- .setGroupConversation(true)
- notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
- val notification = notificationBuilder.build()
- val row = helper.createRow(notification)
-
- val viewHolder =
- inflateSingleLineViewHolder(
- isConversation = true,
- reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
- entry = row.entry,
- context = context,
- logger = mock()
- )
- as HybridConversationNotificationView
- val viewModel =
- SingleLineViewInflater.inflateSingleLineViewModel(
- notification = notification,
- messagingStyle = style,
- builder = notificationBuilder,
- systemUiContext = context,
- )
- // WHEN: binds the viewHolder
- SingleLineConversationViewBinder.bind(
- viewModel,
- viewHolder,
- )
-
- // THEN: the single-line conversation view should be bind with view model's corresponding
- // fields
- assertEquals(viewModel.titleText, viewHolder.titleView.text)
- assertEquals(viewModel.contentText, viewHolder.textView.text)
- assertEquals(
- viewModel.conversationData?.conversationSenderName,
- viewHolder.conversationSenderNameView.text
- )
- }
-
- private companion object {
- const val CHANNEL_ID = "CHANNEL_ID"
- const val CONTENT_TITLE = "CONTENT_TITLE"
- const val CONTENT_TEXT = "CONTENT_TEXT"
- const val USER_NAME = "USER_NAME"
- const val MESSAGE_TEXT = "MESSAGE_TEXT"
- const val SHORTCUT_ID = "Shortcut"
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
index ee819c4..6b3fb5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -16,18 +16,22 @@
package com.android.systemui.statusbar.notification.row
import android.app.Notification
+import android.app.Person
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
-import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePrivateSingleLineView
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflatePublicSingleLineView
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
import com.android.systemui.util.mockito.mock
-import org.junit.Assert
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,14 +63,25 @@
val notification = notificationBuilder.build()
val row: ExpandableNotificationRow = helper.createRow(notification)
- val viewHolder =
- inflateSingleLineViewHolder(
+ val view =
+ inflatePrivateSingleLineView(
isConversation = false,
reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
entry = row.entry,
context = context,
logger = mock()
)
+
+ val publicView =
+ inflatePublicSingleLineView(
+ isConversation = false,
+ reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ assertNotNull(publicView)
+
val viewModel =
SingleLineViewInflater.inflateSingleLineViewModel(
notification = notification,
@@ -76,16 +91,86 @@
)
// WHEN: binds the viewHolder
- SingleLineViewBinder.bind(viewModel, viewHolder)
+ SingleLineViewBinder.bind(viewModel, view)
// THEN: the single-line view should be bind with viewModel's title and content text
- Assert.assertEquals(viewModel.titleText, viewHolder?.titleView?.text)
- Assert.assertEquals(viewModel.contentText, viewHolder?.textView?.text)
+ assertEquals(viewModel.titleText, view?.titleView?.text)
+ assertEquals(viewModel.contentText, view?.textView?.text)
+ }
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindGroupConversationSingleLineView() {
+ // GIVEN a row with a group conversation notification
+ val user =
+ Person.Builder()
+ // .setIcon(Icon.createWithResource(mContext,
+ // R.drawable.ic_account_circle))
+ .setName(USER_NAME)
+ .build()
+ val style =
+ Notification.MessagingStyle(user)
+ .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
+ .addMessage(
+ "How about lunch?",
+ System.currentTimeMillis(),
+ Person.Builder().setName("user2").build()
+ )
+ .setGroupConversation(true)
+ notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
+ val notification = notificationBuilder.build()
+ val row = helper.createRow(notification)
+
+ val view =
+ inflatePrivateSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ as HybridConversationNotificationView
+
+ val publicView =
+ inflatePublicSingleLineView(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ as HybridConversationNotificationView
+ assertNotNull(publicView)
+
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = style,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+ // WHEN: binds the view
+ SingleLineViewBinder.bind(
+ viewModel,
+ view,
+ )
+
+ // THEN: the single-line conversation view should be bound with view model's corresponding
+ // fields
+ assertEquals(viewModel.titleText, view.titleView.text)
+ assertEquals(viewModel.contentText, view.textView.text)
+ assertEquals(
+ viewModel.conversationData?.conversationSenderName,
+ view.conversationSenderNameView.text
+ )
}
private companion object {
const val CHANNEL_ID = "CHANNEL_ID"
const val CONTENT_TITLE = "A Cool New Feature"
const val CONTENT_TEXT = "Checkout out new feature!"
+ const val USER_NAME = "USER_NAME"
+ const val MESSAGE_TEXT = "MESSAGE_TEXT"
+ const val SHORTCUT_ID = "Shortcut"
}
}
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 bd9cccd..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,
@@ -1157,22 +1155,13 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
- public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() {
+ public void updateResources_doesNotUpdateStatusBarWindowHeight() {
mCentralSurfaces.updateResources();
verify(mStatusBarWindowController, never()).refreshStatusBarHeight();
}
@Test
- @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
- public void updateResources_flagDisabled_updatesStatusBarWindowHeight() {
- mCentralSurfaces.updateResources();
-
- verify(mStatusBarWindowController).refreshStatusBarHeight();
- }
-
- @Test
@EnableSceneContainer
public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
mCentralSurfaces.registerCallbacks();
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/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index e670884..eae4f23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -30,7 +30,6 @@
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.Gefingerpoken
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.DarkIconDispatcher
@@ -41,7 +40,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -123,36 +121,21 @@
}
@Test
- fun onAttachedToWindow_flagEnabled_updatesWindowHeight() {
- mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
-
+ fun onAttachedToWindow_updatesWindowHeight() {
view.onAttachedToWindow()
verify(windowController).refreshStatusBarHeight()
}
@Test
- fun onAttachedToWindow_flagDisabled_doesNotUpdateWindowHeight() {
- mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
-
- view.onAttachedToWindow()
-
- verify(windowController, never()).refreshStatusBarHeight()
- }
-
- @Test
- fun onConfigurationChanged_flagEnabled_updatesWindowHeight() {
- mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
-
+ fun onConfigurationChanged_updatesWindowHeight() {
view.onConfigurationChanged(Configuration())
verify(windowController).refreshStatusBarHeight()
}
@Test
- fun onConfigurationChanged_multipleCalls_flagEnabled_updatesWindowHeightMultipleTimes() {
- mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
-
+ fun onConfigurationChanged_multipleCalls_updatesWindowHeightMultipleTimes() {
view.onConfigurationChanged(Configuration())
view.onConfigurationChanged(Configuration())
view.onConfigurationChanged(Configuration())
@@ -162,29 +145,8 @@
}
@Test
- fun onConfigurationChanged_flagDisabled_doesNotUpdateWindowHeight() {
- mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
-
- view.onConfigurationChanged(Configuration())
-
- verify(windowController, never()).refreshStatusBarHeight()
- }
-
- @Test
- fun onConfigurationChanged_multipleCalls_flagDisabled_doesNotUpdateWindowHeight() {
- mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
-
- view.onConfigurationChanged(Configuration())
- view.onConfigurationChanged(Configuration())
- view.onConfigurationChanged(Configuration())
- view.onConfigurationChanged(Configuration())
-
- verify(windowController, never()).refreshStatusBarHeight()
- }
-
- @Test
fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
- val insets = Insets.of(/* left = */ 10, /* top = */ 20, /* right = */ 30, /* bottom = */ 40)
+ val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(insets)
@@ -198,7 +160,7 @@
@Test
fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
- val insets = Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
+ val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(insets)
@@ -213,7 +175,7 @@
@Test
fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() {
val previousInsets =
- Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
+ Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(previousInsets)
context.orCreateTestableResources.overrideConfiguration(Configuration())
@@ -233,7 +195,7 @@
@Test
fun onConfigurationChanged_densityChanged_updatesInsets() {
val previousInsets =
- Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
+ Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(previousInsets)
val configuration = Configuration()
@@ -256,7 +218,7 @@
@Test
fun onConfigurationChanged_fontScaleChanged_updatesInsets() {
val previousInsets =
- Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
+ Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(previousInsets)
val configuration = Configuration()
@@ -291,7 +253,7 @@
@Test
fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
- val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50)
+ val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50)
whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(insets)
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/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
index 6c2e2c6..dfe01bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaRepoTest.kt
@@ -28,7 +28,7 @@
import android.widget.LinearLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS
import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.SysuiTestCase
@@ -39,6 +39,7 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -160,6 +161,47 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun interactorHasOngoingCallNotif_notifIconFlagOff_repoHasNoNotifIcon() =
+ testScope.runTest {
+ val icon = mock<StatusBarIconView>()
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "ongoingNotif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ statusBarChipIcon = icon,
+ whenTime = 567,
+ )
+ )
+
+ val repoState = ongoingCallRepository.ongoingCallState.value
+ assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((repoState as OngoingCallModel.InCall).notificationIconView).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ fun interactorHasOngoingCallNotif_notifIconFlagOn_repoHasNotifIcon() =
+ testScope.runTest {
+ val icon = mock<StatusBarIconView>()
+
+ setNotifOnRepo(
+ activeNotificationModel(
+ key = "ongoingNotif",
+ callType = CallType.Ongoing,
+ uid = CALL_UID,
+ statusBarChipIcon = icon,
+ whenTime = 567,
+ )
+ )
+
+ val repoState = ongoingCallRepository.ongoingCallState.value
+ assertThat(repoState).isInstanceOf(OngoingCallModel.InCall::class.java)
+ assertThat((repoState as OngoingCallModel.InCall).notificationIconView).isEqualTo(icon)
+ }
+
+ @Test
fun notifRepoHasOngoingCallNotif_isOngoingCallNotif_windowControllerUpdated() {
setCallNotifOnRepo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
index cbb8fe8..4c6eaa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -33,7 +34,7 @@
@Test
fun hasOngoingCall_matchesSet() {
- val inCallModel = OngoingCallModel.InCall(startTimeMs = 654, intent = null)
+ val inCallModel = inCallModel(startTimeMs = 654)
underTest.setOngoingCallState(inCallModel)
assertThat(underTest.ongoingCallState.value).isEqualTo(inCallModel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 9d83d5f..8fd0b31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -59,7 +59,6 @@
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.ERI_ON
import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
-import android.telephony.TelephonyManager.EXTRA_DATA_SPN
import android.telephony.TelephonyManager.EXTRA_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
@@ -86,6 +85,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
@@ -93,6 +93,8 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
@@ -110,8 +112,6 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@@ -815,11 +815,9 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.lastValue.onReceive(context, intent)
+ captor.value!!.onReceive(context, intent)
- // spnIntent() sets all values to true and test strings
- assertThat(latest)
- .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
job.cancel()
}
@@ -833,19 +831,17 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.lastValue.onReceive(context, intent)
+ captor.value!!.onReceive(context, intent)
- assertThat(latest)
- .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
// WHEN an intent with a different subId is sent
val wrongSubIntent = spnIntent(subId = 101)
- captor.lastValue.onReceive(context, wrongSubIntent)
+ captor.value!!.onReceive(context, wrongSubIntent)
// THEN the previous intent's name is still used
- assertThat(latest)
- .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
job.cancel()
}
@@ -859,10 +855,9 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.lastValue.onReceive(context, intent)
+ captor.value!!.onReceive(context, intent)
- assertThat(latest)
- .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
val intentWithoutInfo =
spnIntent(
@@ -870,7 +865,7 @@
showPlmn = false,
)
- captor.lastValue.onReceive(context, intentWithoutInfo)
+ captor.value!!.onReceive(context, intentWithoutInfo)
assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
@@ -889,88 +884,10 @@
val intent = spnIntent()
val captor = argumentCaptor<BroadcastReceiver>()
verify(context).registerReceiver(captor.capture(), any())
- captor.lastValue.onReceive(context, intent)
+ captor.value!!.onReceive(context, intent)
// The value is still there despite no active subscribers
- assertThat(underTest.networkName.value)
- .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN$SEP$DATA_SPN"))
- }
-
- @Test
- fun networkName_allFieldsSet() =
- testScope.runTest {
- val latest by collectLastValue(underTest.networkName)
- val captor = argumentCaptor<BroadcastReceiver>()
- verify(context).registerReceiver(captor.capture(), any())
-
- val intent =
- spnIntent(
- subId = SUB_1_ID,
- showSpn = true,
- spn = SPN,
- dataSpn = null,
- showPlmn = true,
- plmn = PLMN,
- )
- captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
- }
-
- @Test
- fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() =
- testScope.runTest {
- val latest by collectLastValue(underTest.networkName)
- val captor = argumentCaptor<BroadcastReceiver>()
- verify(context).registerReceiver(captor.capture(), any())
- val intent =
- spnIntent(
- subId = SUB_1_ID,
- showSpn = true,
- spn = null,
- dataSpn = DATA_SPN,
- showPlmn = true,
- plmn = PLMN,
- )
- captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
- }
-
- @Test
- fun networkName_showPlmn_noShowSPN() =
- testScope.runTest {
- val latest by collectLastValue(underTest.networkName)
- val captor = argumentCaptor<BroadcastReceiver>()
- verify(context).registerReceiver(captor.capture(), any())
- val intent =
- spnIntent(
- subId = SUB_1_ID,
- showSpn = false,
- spn = SPN,
- dataSpn = DATA_SPN,
- showPlmn = true,
- plmn = PLMN,
- )
- captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
- }
-
- @Test
- fun networkName_showPlmn_plmnNull_showSpn() =
- testScope.runTest {
- val latest by collectLastValue(underTest.networkName)
- val captor = argumentCaptor<BroadcastReceiver>()
- verify(context).registerReceiver(captor.capture(), any())
- val intent =
- spnIntent(
- subId = SUB_1_ID,
- showSpn = true,
- spn = SPN,
- dataSpn = DATA_SPN,
- showPlmn = true,
- plmn = null,
- )
- captor.lastValue.onReceive(context, intent)
- assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN$SEP$DATA_SPN"))
+ assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP))
}
@Test
@@ -1211,16 +1128,14 @@
private fun spnIntent(
subId: Int = SUB_1_ID,
showSpn: Boolean = true,
- spn: String? = SPN,
- dataSpn: String? = DATA_SPN,
+ spn: String = SPN,
showPlmn: Boolean = true,
- plmn: String? = PLMN,
+ plmn: String = PLMN,
): Intent =
Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
putExtra(EXTRA_SHOW_SPN, showSpn)
putExtra(EXTRA_SPN, spn)
- putExtra(EXTRA_DATA_SPN, dataSpn)
putExtra(EXTRA_SHOW_PLMN, showPlmn)
putExtra(EXTRA_PLMN, plmn)
}
@@ -1233,7 +1148,6 @@
private const val SEP = "-"
private const val SPN = "testSpn"
- private const val DATA_SPN = "testDataSpn"
private const val PLMN = "testPlmn"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
new file mode 100644
index 0000000..6aefbe9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.gesture
+
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
+import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeGestureMonitorTest : SysuiTestCase() {
+
+ private var gestureState = NOT_STARTED
+ private val gestureMonitor =
+ HomeGestureMonitor(
+ gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
+ gestureStateChangedCallback = { gestureState = it }
+ )
+
+ @Test
+ fun triggersGestureFinishedForThreeFingerGestureUp() {
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = FINISHED)
+ }
+
+ @Test
+ fun triggersGestureProgressForThreeFingerGestureStarted() {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.startEvents(x = 0f, y = 0f),
+ expectedState = IN_PROGRESS
+ )
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onGestureDistanceTooShort() {
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2),
+ expectedState = NOT_STARTED
+ )
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onThreeFingersSwipeInOtherDirections() {
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeDown(), expectedState = NOT_STARTED)
+ assertStateAfterEvents(events = ThreeFingerGesture.swipeLeft(), expectedState = NOT_STARTED)
+ assertStateAfterEvents(
+ events = ThreeFingerGesture.swipeRight(),
+ expectedState = NOT_STARTED
+ )
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onTwoFingersSwipe() {
+ assertStateAfterEvents(events = TwoFingerGesture.swipeUp(), expectedState = NOT_STARTED)
+ }
+
+ @Test
+ fun doesntTriggerGestureFinished_onFourFingersSwipe() {
+ assertStateAfterEvents(events = FourFingerGesture.swipeUp(), expectedState = NOT_STARTED)
+ }
+
+ private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
+ events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ assertThat(gestureState).isEqualTo(expectedState)
+ }
+}
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 bade91a..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
@@ -16,14 +16,13 @@
package com.android.systemui.education.data.repository
+import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.education.data.model.GestureEduModel
-import com.android.systemui.shared.education.GestureType
-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/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index b5ea619..616f2b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -283,7 +283,7 @@
)
}
- override fun updateTransition(
+ override suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
state: TransitionState
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/activatable/ActivatableExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/ActivatableExt.kt
similarity index 71%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/ActivatableExt.kt
index 1f04a44..d265559 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/ActivatableExt.kt
@@ -14,12 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.activatable
+package com.android.systemui.lifecycle
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
/** Activates [activatable] for the duration of the test. */
-fun Activatable.activateIn(testScope: TestScope) {
- testScope.backgroundScope.launch { activate() }
+fun Activatable.activateIn(
+ testScope: TestScope,
+ context: CoroutineContext = EmptyCoroutineContext,
+) {
+ testScope.backgroundScope.launch(context) { activate() }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
new file mode 100644
index 0000000..e8b2dd2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.lifecycle
+
+import kotlinx.coroutines.awaitCancellation
+
+class FakeActivatable(
+ private val onActivation: () -> Unit = {},
+ private val onDeactivation: () -> Unit = {},
+) : SafeActivatable() {
+ var activationCount = 0
+ var cancellationCount = 0
+
+ override suspend fun onActivated() {
+ activationCount++
+ onActivation()
+ try {
+ awaitCancellation()
+ } finally {
+ cancellationCount++
+ onDeactivation()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
new file mode 100644
index 0000000..9a56f24
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.lifecycle
+
+import kotlinx.coroutines.awaitCancellation
+
+class FakeSysUiViewModel(
+ private val onActivation: () -> Unit = {},
+ private val onDeactivation: () -> Unit = {},
+) : SysUiViewModel() {
+ var activationCount = 0
+ var cancellationCount = 0
+
+ override suspend fun onActivated() {
+ activationCount++
+ onActivation()
+ try {
+ awaitCancellation()
+ } finally {
+ cancellationCount++
+ onDeactivation()
+ }
+ }
+}
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/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 37f1f13..76bdc0d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -18,6 +18,7 @@
import android.app.PendingIntent
import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
@@ -36,6 +37,7 @@
aodIcon: Icon? = null,
shelfIcon: Icon? = null,
statusBarIcon: Icon? = null,
+ statusBarChipIcon: StatusBarIconView? = null,
uid: Int = 0,
instanceId: Int? = null,
isGroupSummary: Boolean = false,
@@ -57,6 +59,7 @@
aodIcon = aodIcon,
shelfIcon = shelfIcon,
statusBarIcon = statusBarIcon,
+ statusBarChipIconView = statusBarChipIcon,
uid = uid,
packageName = packageName,
contentIntent = contentIntent,
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/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
new file mode 100644
index 0000000..3963d7c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.statusbar.phone.ongoingcall.shared.model
+
+import android.app.PendingIntent
+import com.android.systemui.statusbar.StatusBarIconView
+
+/** Helper for building [OngoingCallModel.InCall] instances in tests. */
+fun inCallModel(
+ startTimeMs: Long,
+ notificationIcon: StatusBarIconView? = null,
+ intent: PendingIntent? = null
+) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent)
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/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index cbbce1a..8663593 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -120,6 +120,14 @@
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
+ static final String DEVICE_CONFIG_WIDTH = "device_config_width";
+
+ static final String DEVICE_CONFIG_HEIGHT = "device_config_height";
+
+ static final String DEVICE_CONFIG_SECONDARY_WIDTH = "device_config_secondary_width";
+
+ static final String DEVICE_CONFIG_SECONDARY_HEIGHT = "device_config_secondary_height";
+
static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f;
// If this file exists, it means we exceeded our quota last time
@@ -175,6 +183,16 @@
// disk churn.
final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1);
final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1);
+
+ final int deviceConfigWidth = sharedPrefs.getInt(
+ DEVICE_CONFIG_WIDTH, /* defValue= */ -1);
+ final int deviceConfigHeight = sharedPrefs.getInt(
+ DEVICE_CONFIG_HEIGHT, /* defValue= */ -1);
+ final int deviceConfigSecondaryWidth = sharedPrefs.getInt(
+ DEVICE_CONFIG_SECONDARY_WIDTH, /* defValue= */ -1);
+ final int deviceConfigSecondaryHeight = sharedPrefs.getInt(
+ DEVICE_CONFIG_SECONDARY_HEIGHT, /* defValue= */ -1);
+
final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK);
final boolean sysChanged = (sysGeneration != lastSysGeneration);
@@ -195,7 +213,11 @@
backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
- backupDeviceInfoFile(data);
+
+ final boolean isDeviceConfigChanged = isDeviceConfigChanged(deviceConfigWidth,
+ deviceConfigHeight, deviceConfigSecondaryWidth, deviceConfigSecondaryHeight);
+
+ backupDeviceInfoFile(sharedPrefs, isDeviceConfigChanged, data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
mEventLogger.onBackupException(e);
@@ -209,50 +231,72 @@
}
}
+ private boolean isDeviceConfigChanged(int width, int height, int secondaryWidth,
+ int secondaryHeight) {
+ Point currentDimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point currentSecondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
+
+ return (currentDimensions.x != width
+ || currentDimensions.y != height
+ || currentSecondaryDimensions.x != secondaryWidth
+ || currentSecondaryDimensions.y != secondaryHeight);
+ }
+
/**
* This method backs up the device dimension information. The device data will always get
* overwritten when triggering a backup
*/
- private void backupDeviceInfoFile(FullBackupDataOutput data)
+ private void backupDeviceInfoFile(SharedPreferences sharedPrefs, boolean isDeviceConfigChanged,
+ FullBackupDataOutput data)
throws IOException {
final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
- // save the dimensions of the device with xml formatting
- Point dimensions = getScreenDimensions();
- Display smallerDisplay = getSmallerDisplayIfExists();
- Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
- new Point(0, 0);
+ if (isDeviceConfigChanged) {
+ // save the dimensions of the device with xml formatting
+ Point dimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
- deviceInfoStage.createNewFile();
- FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
- TypedXmlSerializer out = Xml.resolveSerializer(fstream);
- out.startDocument(null, true);
- out.startTag(null, "dimensions");
+ deviceInfoStage.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "dimensions");
- out.startTag(null, "width");
- out.text(String.valueOf(dimensions.x));
- out.endTag(null, "width");
+ out.startTag(null, "width");
+ out.text(String.valueOf(dimensions.x));
+ out.endTag(null, "width");
- out.startTag(null, "height");
- out.text(String.valueOf(dimensions.y));
- out.endTag(null, "height");
+ out.startTag(null, "height");
+ out.text(String.valueOf(dimensions.y));
+ out.endTag(null, "height");
- if (smallerDisplay != null) {
- out.startTag(null, "secondarywidth");
- out.text(String.valueOf(secondaryDimensions.x));
- out.endTag(null, "secondarywidth");
+ if (smallerDisplay != null) {
+ out.startTag(null, "secondarywidth");
+ out.text(String.valueOf(secondaryDimensions.x));
+ out.endTag(null, "secondarywidth");
- out.startTag(null, "secondaryheight");
- out.text(String.valueOf(secondaryDimensions.y));
- out.endTag(null, "secondaryheight");
+ out.startTag(null, "secondaryheight");
+ out.text(String.valueOf(secondaryDimensions.y));
+ out.endTag(null, "secondaryheight");
+ }
+
+ out.endTag(null, "dimensions");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+
+ SharedPreferences.Editor editor = sharedPrefs.edit();
+ editor.putInt(DEVICE_CONFIG_WIDTH, dimensions.x);
+ editor.putInt(DEVICE_CONFIG_HEIGHT, dimensions.y);
+ editor.putInt(DEVICE_CONFIG_SECONDARY_WIDTH, secondaryDimensions.x);
+ editor.putInt(DEVICE_CONFIG_SECONDARY_HEIGHT, secondaryDimensions.y);
+ editor.apply();
}
-
- out.endTag(null, "dimensions");
- out.endDocument();
- fstream.flush();
- FileUtils.sync(fstream);
- fstream.close();
-
if (DEBUG) Slog.v(TAG, "Storing device dimension data");
backupFile(deviceInfoStage, data);
}
diff --git a/packages/overlays/HsumDefaultConfigOverlay/Android.bp b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
new file mode 100644
index 0000000..bff2f9b
--- /dev/null
+++ b/packages/overlays/HsumDefaultConfigOverlay/Android.bp
@@ -0,0 +1,16 @@
+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"],
+}
+
+runtime_resource_overlay {
+ name: "HsumDefaultConfigOverlay",
+ certificate: "platform",
+
+ product_specific: true,
+ sdk_version: "current",
+}
diff --git a/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..dcd1741
--- /dev/null
+++ b/packages/overlays/HsumDefaultConfigOverlay/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.overlay.hsum.defaultconfig"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android" android:priority="2" android:isStatic="true" />
+</manifest>
diff --git a/packages/overlays/HsumDefaultConfigOverlay/OWNERS b/packages/overlays/HsumDefaultConfigOverlay/OWNERS
new file mode 100644
index 0000000..79dd1c9
--- /dev/null
+++ b/packages/overlays/HsumDefaultConfigOverlay/OWNERS
@@ -0,0 +1,2 @@
+# People who can approve submission
+include platform/frameworks/base:/MULTIUSER_OWNERS
diff --git a/packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml b/packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
new file mode 100644
index 0000000..7dbdfc7
--- /dev/null
+++ b/packages/overlays/HsumDefaultConfigOverlay/res/values/config.xml
@@ -0,0 +1,38 @@
+<?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.
+ -->
+
+<!-- Default configuration for Headless System User Mode (HSUM) builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
+ with admin privileges and admin privileges can be granted/revoked from existing users. -->
+ <bool name="config_enableMultipleAdmins">true</bool>
+
+ <!-- Whether the device will automatically (at first boot) have a designated main user and treat
+ it as a permanent admin.
+ Since the main user is a permanent admin user it can't be deleted or downgraded to
+ non-admin status.
+ This is generally only relevant on headless system user mode (HSUM) devices; on other
+ devices, the main user is the system user which is always a permanent admin anyway.
+ Note that HSUM devices without this enabled will not automatically have a main user. -->
+ <bool name="config_isMainUserPermanentAdmin">true</bool>
+
+ <!-- Maximum number of users we allow to be running at a time.
+ Note that this includes the headless system user. -->
+ <integer name="config_multiuserMaxRunningUsers">4</integer>
+
+</resources>
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 7c8fd42..6150343 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -181,13 +181,13 @@
visibility: ["//visibility:public"],
}
-// Carefully compiles against only test_current to support tests that
+// Carefully compiles against only module_current to support tests that
// want to verify they're unbundled. The "impl" library above is what
// ships inside the Ravenwood environment to actually drive any API
// access to implementation details.
-// This library needs to be statically linked to mainline tests as well,
-// which need to be able to run on multiple API levels, so we can't use
-// test APIs in this module.
+// We can't use test_current here because this library needs to be statically
+// linked to mainline tests as well, which can't use test APIs because they
+// need to be able to run on multiple API levels.
java_library {
name: "ravenwood-junit",
srcs: [
@@ -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/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java
index ee28099..0238baa 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/JvmWorkaround.java
@@ -16,6 +16,7 @@
package com.android.ravenwood.common;
import java.io.FileDescriptor;
+import java.io.IOException;
/**
* Collection of methods to workaround limitation in the hostside JVM.
@@ -44,6 +45,11 @@
public abstract int getFdInt(FileDescriptor fd);
/**
+ * Equivalent to Android's Os.close(fd).
+ */
+ public abstract void closeFd(FileDescriptor fd) throws IOException;
+
+ /**
* Placeholder implementation for the host side.
*
* Even on the host side, we don't want to throw just because the class is loaded,
@@ -64,5 +70,10 @@
public int getFdInt(FileDescriptor fd) {
throw calledOnHostside();
}
+
+ @Override
+ public void closeFd(FileDescriptor fd) {
+ throw calledOnHostside();
+ }
}
}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java
index 9aedaab..a260147 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/OpenJdkWorkaround.java
@@ -16,6 +16,8 @@
package com.android.ravenwood.common;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
class OpenJdkWorkaround extends JvmWorkaround {
@Override
@@ -43,4 +45,19 @@
+ " perhaps JRE has changed?", e);
}
}
+
+ @Override
+ public void closeFd(FileDescriptor fd) throws IOException {
+ try {
+ final Object obj = Class.forName("jdk.internal.access.SharedSecrets").getMethod(
+ "getJavaIOFileDescriptorAccess").invoke(null);
+ Class.forName("jdk.internal.access.JavaIOFileDescriptorAccess").getMethod(
+ "close", FileDescriptor.class).invoke(obj, fd);
+ } catch (InvocationTargetException e) {
+ SneakyThrow.sneakyThrow(e.getTargetException());
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Failed to interact with raw FileDescriptor internals;"
+ + " perhaps JRE has changed?", e);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/ravenwood/runtime-common-src/com/android/ravenwood/common/SneakyThrow.java
similarity index 60%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
rename to ravenwood/runtime-common-src/com/android/ravenwood/common/SneakyThrow.java
index 14b9e7f..0dbf7df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/SneakyThrow.java
@@ -13,15 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.wm.shell.windowdecor.common
+package com.android.ravenwood.common;
-/** A callback to be invoked when a Task's window decor element is clicked. */
-fun interface OnTaskActionClickListener {
+public class SneakyThrow {
+
+ private SneakyThrow() {
+ }
+
/**
- * Called when a task's decor element has been clicked.
- *
- * @param taskId the id of the task.
- * @param tag a readable identifier for the element.
+ * Throw checked exceptions without the need to declare in method signature
*/
- fun onClick(taskId: Int, tag: String)
+ public static void sneakyThrow(Throwable t) {
+ SneakyThrow.<RuntimeException>sneakyThrow_(t);
+ }
+
+ private static <T extends Throwable> void sneakyThrow_(Throwable t) throws T {
+ throw (T) t;
+ }
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
index 1a15d7a..5a3589d 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
@@ -16,105 +16,16 @@
package com.android.platform.test.ravenwood.nativesubstitution;
-import static android.os.ParcelFileDescriptor.MODE_APPEND;
-import static android.os.ParcelFileDescriptor.MODE_CREATE;
-import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
-import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
-import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
-import static android.os.ParcelFileDescriptor.MODE_WORLD_READABLE;
-import static android.os.ParcelFileDescriptor.MODE_WORLD_WRITEABLE;
-import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
import com.android.ravenwood.common.JvmWorkaround;
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Map;
public class ParcelFileDescriptor_host {
- private static final String TAG = "ParcelFileDescriptor_host";
-
- /**
- * Since we don't have a great way to keep an unmanaged {@code FileDescriptor} reference
- * alive, we keep a strong reference to the {@code RandomAccessFile} we used to open it. This
- * gives us a way to look up the original parent object when closing later.
- */
- @GuardedBy("sActive")
- private static final Map<FileDescriptor, RandomAccessFile> sActive = new HashMap<>();
-
- public static void native_setFdInt$ravenwood(FileDescriptor fd, int fdInt) {
+ public static void setFdInt(FileDescriptor fd, int fdInt) {
JvmWorkaround.getInstance().setFdInt(fd, fdInt);
}
- public static int native_getFdInt$ravenwood(FileDescriptor fd) {
+ public static int getFdInt(FileDescriptor fd) {
return JvmWorkaround.getInstance().getFdInt(fd);
}
-
- public static FileDescriptor native_open$ravenwood(File file, int pfdMode) throws IOException {
- if ((pfdMode & MODE_CREATE) != 0 && !file.exists()) {
- throw new FileNotFoundException();
- }
-
- final String modeString;
- if ((pfdMode & MODE_READ_WRITE) == MODE_READ_WRITE) {
- modeString = "rw";
- } else if ((pfdMode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
- modeString = "rw";
- } else if ((pfdMode & MODE_READ_ONLY) == MODE_READ_ONLY) {
- modeString = "r";
- } else {
- throw new IllegalArgumentException();
- }
-
- final RandomAccessFile raf = new RandomAccessFile(file, modeString);
-
- // Now that we have a real file on disk, match requested flags
- if ((pfdMode & MODE_TRUNCATE) != 0) {
- raf.setLength(0);
- }
- if ((pfdMode & MODE_APPEND) != 0) {
- raf.seek(raf.length());
- }
- if ((pfdMode & MODE_WORLD_READABLE) != 0) {
- file.setReadable(true, false);
- }
- if ((pfdMode & MODE_WORLD_WRITEABLE) != 0) {
- file.setWritable(true, false);
- }
-
- final FileDescriptor fd = raf.getFD();
- synchronized (sActive) {
- sActive.put(fd, raf);
- }
- return fd;
- }
-
- public static void native_close$ravenwood(FileDescriptor fd) {
- final RandomAccessFile raf;
- synchronized (sActive) {
- raf = sActive.remove(fd);
- }
- int fdInt = JvmWorkaround.getInstance().getFdInt(fd);
- try {
- if (raf != null) {
- raf.close();
- } else {
- // This FD wasn't created by native_open$ravenwood().
- // The FD was passed to the PFD ctor. Just close it.
- Os.close(fd);
- }
- } catch (IOException | ErrnoException e) {
- Log.w(TAG, "Exception thrown while closing fd " + fdInt, e);
- }
- }
}
-;
\ No newline at end of file
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index 825ab72..ecaa816 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -15,9 +15,11 @@
*/
package android.system;
+import com.android.ravenwood.common.JvmWorkaround;
import com.android.ravenwood.common.RavenwoodRuntimeNative;
import java.io.FileDescriptor;
+import java.io.IOException;
/**
* OS class replacement used on Ravenwood. For now, we just implement APIs as we need them...
@@ -56,6 +58,15 @@
/** Ravenwood version of the OS API. */
public static void close(FileDescriptor fd) throws ErrnoException {
- RavenwoodRuntimeNative.close(fd);
+ try {
+ JvmWorkaround.getInstance().closeFd(fd);
+ } catch (IOException e) {
+ // The only valid error on Linux that can happen is EIO
+ throw new ErrnoException("close", OsConstants.EIO);
+ }
+ }
+
+ public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
+ return RavenwoodRuntimeNative.open(path, flags, mode);
}
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
index 2bc8e71..beba833 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
@@ -48,7 +48,7 @@
public static native StructStat stat(String path) throws ErrnoException;
- private static native void nClose(int fd) throws ErrnoException;
+ private static native int nOpen(String path, int flags, int mode) throws ErrnoException;
public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
@@ -69,7 +69,7 @@
public static FileDescriptor dup(FileDescriptor fd) throws ErrnoException {
var fdInt = nDup(JvmWorkaround.getInstance().getFdInt(fd));
- var retFd = new java.io.FileDescriptor();
+ var retFd = new FileDescriptor();
JvmWorkaround.getInstance().setFdInt(retFd, fdInt);
return retFd;
}
@@ -86,10 +86,11 @@
return nFstat(fdInt);
}
- /** See close(2) */
- public static void close(FileDescriptor fd) throws ErrnoException {
- var fdInt = JvmWorkaround.getInstance().getFdInt(fd);
-
- nClose(fdInt);
+ public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
+ int fd = nOpen(path, flags, mode);
+ if (fd < 0) return null;
+ var retFd = new FileDescriptor();
+ JvmWorkaround.getInstance().setFdInt(retFd, fd);
+ return retFd;
}
}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index ee84954..c804928 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -18,9 +18,11 @@
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
+#include <string>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
#include "jni.h"
#include "utils/Log.h"
@@ -49,6 +51,43 @@
static jclass g_StructStat;
static jclass g_StructTimespecClass;
+// We have to explicitly decode the string to real UTF-8, because when using GetStringUTFChars
+// we only get modified UTF-8, which is not the platform string type used in host JVM.
+struct ScopedRealUtf8Chars {
+ ScopedRealUtf8Chars(JNIEnv* env, jstring s) : valid_(false) {
+ if (s == nullptr) {
+ jniThrowNullPointerException(env);
+ return;
+ }
+ jclass clazz = env->GetObjectClass(s);
+ jmethodID getBytes = env->GetMethodID(clazz, "getBytes", "(Ljava/lang/String;)[B");
+
+ ScopedLocalRef<jstring> utf8(env, env->NewStringUTF("UTF-8"));
+ ScopedLocalRef<jbyteArray> jbytes(env,
+ (jbyteArray) env->CallObjectMethod(s, getBytes, utf8.get()));
+
+ ScopedByteArrayRO bytes(env, jbytes.get());
+ string_.append((const char *) bytes.get(), bytes.size());
+ valid_ = true;
+ }
+
+ const char* c_str() const {
+ return valid_ ? string_.c_str() : nullptr;
+ }
+
+ size_t size() const {
+ return string_.size();
+ }
+
+ const char& operator[](size_t n) const {
+ return string_[n];
+ }
+
+private:
+ std::string string_;
+ bool valid_;
+};
+
static jclass findClass(JNIEnv* env, const char* name) {
ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
@@ -99,7 +138,7 @@
}
static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) {
- ScopedUtfChars path(env, javaPath);
+ ScopedRealUtf8Chars path(env, javaPath);
if (path.c_str() == NULL) {
return NULL;
}
@@ -167,9 +206,12 @@
return doStat(env, javaPath, false);
}
-static void nClose(JNIEnv* env, jclass, jint fd) {
- // Don't use TEMP_FAILURE_RETRY() on close(): https://lkml.org/lkml/2005/9/10/129
- throwIfMinusOne(env, "close", close(fd));
+static jint Linux_open(JNIEnv* env, jobject, jstring javaPath, jint flags, jint mode) {
+ ScopedRealUtf8Chars path(env, javaPath);
+ if (path.c_str() == NULL) {
+ return -1;
+ }
+ return throwIfMinusOne(env, "open", TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)));
}
// ---- Registration ----
@@ -184,7 +226,7 @@
{ "nFstat", "(I)Landroid/system/StructStat;", (void*)nFstat },
{ "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat },
{ "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
- { "nClose", "(I)V", (void*)nClose },
+ { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
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/ravenwood/texts/ravenwood-framework-jarjar-rules.txt b/ravenwood/texts/ravenwood-framework-jarjar-rules.txt
index 2eeb904..afef564 100644
--- a/ravenwood/texts/ravenwood-framework-jarjar-rules.txt
+++ b/ravenwood/texts/ravenwood-framework-jarjar-rules.txt
@@ -1,2 +1 @@
-# To avoid VerifyError on nano proto files (b/324063814)
-rule com.**.nano.** devicenano.@0
+# Applying jarjar on framework-minux-apex is too slow, so we don't use jarjar for now. b/313930116
\ No newline at end of file
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 9d29a05..4012bdc 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -9,6 +9,11 @@
# Keep all sysprops generated code implementations
class :sysprops keepclass
+# To avoid VerifyError on nano proto files (b/324063814), we rename nano proto classes.
+# Note: The "rename" directive must use shashes (/) as a package name separator.
+rename com/.*/nano/ devicenano/
+rename android/.*/nano/ devicenano/
+
# Exported to Mainline modules; cannot use annotations
class com.android.internal.util.FastXmlSerializer keepclass
class com.android.internal.util.FileRotator keepclass
diff --git a/services/Android.bp b/services/Android.bp
index dce6aa7..ded7379 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -108,6 +108,8 @@
filegroup {
name: "services-non-updatable-sources",
srcs: [
+ ":incremental_aidl",
+ ":services.core-aidl-sources",
":services.core-sources",
":services.core-sources-am-wm",
"core/java/com/android/server/am/package.html",
@@ -377,4 +379,8 @@
},
},
api_surface: "system-server",
+ sdk_version: "module_current",
+ libs: [
+ "framework-annotations-lib",
+ ],
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 099cb28..d16a665 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -45,9 +45,11 @@
import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
+import com.android.server.accessibility.magnification.MouseEventHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
import com.android.server.policy.WindowManagerPolicy;
@@ -864,15 +866,21 @@
TYPE_MAGNIFICATION_OVERLAY, null /* options */);
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper =
new FullScreenMagnificationVibrationHelper(uiContext);
- magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext,
- mAms.getMagnificationController().getFullScreenMagnificationController(),
- mAms.getTraceManager(),
- mAms.getMagnificationController(),
- detectControlGestures,
- detectTwoFingerTripleTap,
- triggerable,
- new WindowMagnificationPromptController(displayContext, mUserId), displayId,
- fullScreenMagnificationVibrationHelper);
+ FullScreenMagnificationController controller =
+ mAms.getMagnificationController().getFullScreenMagnificationController();
+ magnificationGestureHandler =
+ new FullScreenMagnificationGestureHandler(
+ uiContext,
+ controller,
+ mAms.getTraceManager(),
+ mAms.getMagnificationController(),
+ detectControlGestures,
+ detectTwoFingerTripleTap,
+ triggerable,
+ new WindowMagnificationPromptController(displayContext, mUserId),
+ displayId,
+ fullScreenMagnificationVibrationHelper,
+ new MouseEventHandler(controller));
}
return magnificationGestureHandler;
}
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/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index b5b998f..6b6b39d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -1131,7 +1131,10 @@
}
if (isAlwaysOnMagnificationEnabled()) {
- zoomOutFromService(displayId);
+ if (!mControllerCtx.getContext().getResources().getBoolean(
+ R.bool.config_magnification_keep_zoom_level_when_context_changed)) {
+ zoomOutFromService(displayId);
+ }
} else {
reset(displayId, true);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 159022b..b052d23 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.magnification;
+import static android.view.InputDevice.SOURCE_MOUSE;
+import static android.view.InputDevice.SOURCE_STYLUS;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -183,7 +185,10 @@
private final int mMinimumVelocity;
private final int mMaximumVelocity;
- public FullScreenMagnificationGestureHandler(@UiContext Context context,
+ private MouseEventHandler mMouseEventHandler;
+
+ public FullScreenMagnificationGestureHandler(
+ @UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
AccessibilityTraceManager trace,
Callback callback,
@@ -192,7 +197,8 @@
boolean detectShortcutTrigger,
@NonNull WindowMagnificationPromptController promptController,
int displayId,
- FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
+ FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
+ MouseEventHandler mouseEventHandler) {
this(
context,
fullScreenMagnificationController,
@@ -207,9 +213,8 @@
/* magnificationLogger= */ null,
ViewConfiguration.get(context),
new OneFingerPanningSettingsProvider(
- context,
- Flags.enableMagnificationOneFingerPanningGesture()
- ));
+ context, Flags.enableMagnificationOneFingerPanningGesture()),
+ mouseEventHandler);
}
/** Constructor for tests. */
@@ -227,8 +232,8 @@
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
MagnificationLogger magnificationLogger,
ViewConfiguration viewConfiguration,
- OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider
- ) {
+ OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider,
+ MouseEventHandler mouseEventHandler) {
super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
@@ -318,6 +323,7 @@
mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize(
R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop);
mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ mMouseEventHandler = mouseEventHandler;
if (mDetectShortcutTrigger) {
mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -331,15 +337,28 @@
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (event.getActionMasked() == ACTION_DOWN) {
- cancelFling();
- }
+ if (event.getSource() == SOURCE_TOUCHSCREEN) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ cancelFling();
+ }
+ handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags);
+ } else if (Flags.enableMagnificationFollowsMouse()
+ && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) {
+ if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
+ // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
+ // over, rather than only interacting with the current display.
- handleEventWith(mCurrentState, event, rawEvent, policyFlags);
+ // Send through the mouse/stylus event handler.
+ mMouseEventHandler.onEvent(event, mDisplayId);
+ }
+ // Dispatch to normal event handling flow.
+ dispatchTransformedEvent(event, rawEvent, policyFlags);
+ }
}
- private void handleEventWith(State stateHandler,
- MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ private void handleTouchEventWith(
+ State stateHandler, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+
// To keep InputEventConsistencyVerifiers within GestureDetectors happy
mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
@@ -1421,6 +1440,11 @@
protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
+ if (Flags.enableMagnificationFollowsMouse()
+ && !event.isFromSource(SOURCE_TOUCHSCREEN)) {
+ // Only touch events need to be cached and sent later.
+ return;
+ }
if (event.getActionMasked() == ACTION_DOWN) {
mPreLastDown = mLastDown;
mLastDown = MotionEvent.obtain(event);
@@ -1458,7 +1482,7 @@
mDelayedEventQueue = info.mNext;
info.event.setDownTime(info.event.getDownTime() + offset);
- handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
+ handleTouchEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
info.recycle();
} while (mDelayedEventQueue != null);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 11d0713..08411c2 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.magnification;
+import static android.view.InputDevice.SOURCE_MOUSE;
+import static android.view.InputDevice.SOURCE_STYLUS;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_UP;
@@ -139,12 +141,35 @@
}
}
+ /**
+ * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the
+ * event should not be dispatched to the magnifier.
+ *
+ * @param event The event to check.
+ * @return `true` if the event should be sent through the normal event flow or `false` if it
+ * should be observed by magnifier.
+ */
private boolean shouldDispatchTransformedEvent(MotionEvent event) {
- if ((!mDetectSingleFingerTripleTap && !mDetectTwoFingerTripleTap && !mDetectShortcutTrigger)
- || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
- return true;
+ if (event.getSource() == SOURCE_TOUCHSCREEN) {
+ if (mDetectSingleFingerTripleTap
+ || mDetectTwoFingerTripleTap
+ || mDetectShortcutTrigger) {
+ // Observe touchscreen events while magnification activation is detected.
+ return false;
+ }
}
- return false;
+ if (Flags.enableMagnificationFollowsMouse()) {
+ if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) {
+ // Note that mouse events include other mouse-like pointing devices
+ // such as touchpads and pointing sticks.
+ // Observe any mouse or stylus movement.
+ // We observe all movement to ensure that events continue to come in order,
+ // even though only some movement types actually move the viewport.
+ return false;
+ }
+ }
+ // Magnification dispatches (ignores) all other events
+ return true;
}
final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
new file mode 100644
index 0000000..845249e
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static android.view.InputDevice.SOURCE_MOUSE;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static android.view.MotionEvent.ACTION_MOVE;
+
+import android.view.MotionEvent;
+
+import com.android.server.accessibility.AccessibilityManagerService;
+
+/** MouseEventHandler handles mouse and stylus events that should move the viewport. */
+public final class MouseEventHandler {
+ private final FullScreenMagnificationController mFullScreenMagnificationController;
+
+ public MouseEventHandler(FullScreenMagnificationController fullScreenMagnificationController) {
+ mFullScreenMagnificationController = fullScreenMagnificationController;
+ }
+
+ /**
+ * Handles a mouse or stylus event, moving the magnifier if needed.
+ *
+ * @param event The mouse or stylus MotionEvent to consume
+ * @param displayId The display that is being magnified
+ */
+ public void onEvent(MotionEvent event, int displayId) {
+ if (event.getAction() == ACTION_HOVER_MOVE
+ || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE)) {
+ final float eventX = event.getX();
+ final float eventY = event.getY();
+
+ // Only move the viewport when over a magnified region.
+ // TODO(b/354696546): Ensure this doesn't stop the viewport from reaching the
+ // corners and edges at high levels of magnification.
+ if (mFullScreenMagnificationController.magnificationRegionContains(
+ displayId, eventX, eventY)) {
+ mFullScreenMagnificationController.setCenter(
+ displayId,
+ eventX,
+ eventY,
+ /* animate= */ false,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 6f1141f..1818cdd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -148,6 +148,10 @@
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getSource() != SOURCE_TOUCHSCREEN) {
+ // Window Magnification viewport doesn't move with mouse events (yet).
+ return;
+ }
// To keep InputEventConsistencyVerifiers within GestureDetectors happy.
mObservePanningScalingState.mPanningScalingHandler.onTouchEvent(event);
mCurrentState.onMotionEvent(event, rawEvent, policyFlags);
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/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 0bf931c..e57817f 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -28,12 +28,12 @@
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
-import android.companion.virtual.flags.Flags;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.Handler;
@@ -52,6 +52,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
/**
* A controller to control the policies of the windows that can be displayed on the virtual display.
@@ -74,7 +75,7 @@
*/
public interface ActivityBlockedCallback {
/** Called when an activity is blocked.*/
- void onActivityBlocked(int displayId, ActivityInfo activityInfo);
+ void onActivityBlocked(int displayId, ActivityInfo activityInfo, IntentSender intentSender);
}
private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
new ComponentName("android", BlockedAppStreamingActivity.class.getName());
@@ -113,7 +114,6 @@
@NonNull
private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
@Nullable
- private final ComponentName mPermissionDialogComponent;
private final Object mGenericWindowPolicyControllerLock = new Object();
@Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
@@ -178,7 +178,6 @@
@NonNull Set<ComponentName> activityPolicyExemptions,
boolean crossTaskNavigationAllowedByDefault,
@NonNull Set<ComponentName> crossTaskNavigationExemptions,
- @Nullable ComponentName permissionDialogComponent,
@Nullable ActivityListener activityListener,
@Nullable ActivityBlockedCallback activityBlockedCallback,
@Nullable SecureWindowCallback secureWindowCallback,
@@ -193,7 +192,6 @@
mActivityPolicyExemptions = new ArraySet<>(activityPolicyExemptions);
mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
- mPermissionDialogComponent = permissionDialogComponent;
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
@@ -286,29 +284,20 @@
@Override
public boolean canActivityBeLaunched(@NonNull ActivityInfo activityInfo,
@Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
- int launchingFromDisplayId, boolean isNewTask) {
- if (Flags.interceptIntentsBeforeApplyingPolicy()) {
- if (mIntentListenerCallback != null && intent != null
- && mIntentListenerCallback.shouldInterceptIntent(intent)) {
- logActivityLaunchBlocked("Virtual device intercepting intent");
- return false;
- }
- if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
- isNewTask)) {
- notifyActivityBlocked(activityInfo);
- return false;
- }
- } else {
- if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
- isNewTask)) {
- notifyActivityBlocked(activityInfo);
- return false;
- }
- if (mIntentListenerCallback != null && intent != null
- && mIntentListenerCallback.shouldInterceptIntent(intent)) {
- logActivityLaunchBlocked("Virtual device intercepting intent");
- return false;
- }
+ int launchingFromDisplayId, boolean isNewTask, boolean isResultExpected,
+ @Nullable Supplier<IntentSender> intentSender) {
+ if (mIntentListenerCallback != null && intent != null
+ && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+ logActivityLaunchBlocked("Virtual device intercepting intent");
+ return false;
+ }
+ if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
+ isNewTask)) {
+ // If the sender of the original intent expects a result to be reported, do not pass the
+ // intent sender to the client callback. As the launch is blocked, the caller already
+ // received that activity result.
+ notifyActivityBlocked(activityInfo, isResultExpected ? null : intentSender);
+ return false;
}
return true;
}
@@ -370,14 +359,6 @@
return false;
}
- // mPermissionDialogComponent being null means we don't want to block permission Dialogs
- // based on FLAG_STREAM_PERMISSIONS
- if (mPermissionDialogComponent != null
- && mPermissionDialogComponent.equals(activityComponent)) {
- logActivityLaunchBlocked("Permission dialog not allowed on virtual device");
- return false;
- }
-
return true;
}
@@ -406,7 +387,7 @@
// TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
if ((windowFlags & FLAG_SECURE) != 0
|| (systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
- notifyActivityBlocked(activityInfo);
+ notifyActivityBlocked(activityInfo, /* intentSender= */ null);
return false;
}
}
@@ -479,19 +460,19 @@
&& mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
}
- private void notifyActivityBlocked(ActivityInfo activityInfo) {
+ private void notifyActivityBlocked(
+ ActivityInfo activityInfo, Supplier<IntentSender> intentSender) {
int displayId = waitAndGetDisplayId();
// Don't trigger activity blocked callback for mirror displays, because we can't show
// any activity or presentation on it anyway.
if (!waitAndGetIsMirrorDisplay() && mActivityBlockedCallback != null
&& displayId != INVALID_DISPLAY) {
- mActivityBlockedCallback.onActivityBlocked(displayId, activityInfo);
+ mActivityBlockedCallback.onActivityBlocked(displayId, activityInfo,
+ intentSender == null ? null : intentSender.get());
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_activity_blocked_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_activity_blocked_count",
+ mAttributionSource.getUid());
}
private static boolean isAllowedByPolicy(boolean allowedByDefault,
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d091ce8..8da58cf 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -842,11 +842,9 @@
deviceName, inputDeviceId));
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- String metricId = getMetricIdForInputType(type);
- if (metricId != null) {
- Counter.logIncrementWithUid(metricId, mAttributionSource.getUid());
- }
+ String metricId = getMetricIdForInputType(type);
+ if (metricId != null) {
+ Counter.logIncrementWithUid(metricId, mAttributionSource.getUid());
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 0655685..8d075db 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -145,11 +145,9 @@
mSensorDescriptors.put(sensorToken, sensorDescriptor);
mVirtualSensors.put(handle, sensor);
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_sensors_created_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_sensors_created_count",
+ mAttributionSource.getUid());
}
boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 29ddf64..2db5443 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,8 +29,6 @@
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
-import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix;
-import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -62,6 +60,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.PointF;
@@ -208,7 +207,6 @@
@GuardedBy("mVirtualDeviceLock")
@NonNull
private final Set<ComponentName> mActivityPolicyExemptions;
- private final ComponentName mPermissionDialogComponent;
private ActivityListener createListenerAdapter() {
return new ActivityListener() {
@@ -244,9 +242,11 @@
@Override
public void onActivityLaunchBlocked(int displayId,
- @NonNull ComponentName componentName, @UserIdInt int userId) {
+ @NonNull ComponentName componentName, @UserIdInt int userId,
+ @Nullable IntentSender intentSender) {
try {
- mActivityListener.onActivityLaunchBlocked(displayId, componentName, userId);
+ mActivityListener.onActivityLaunchBlocked(
+ displayId, componentName, userId, intentSender);
} catch (RemoteException e) {
Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
@@ -343,11 +343,6 @@
if (mCameraAccessController != null) {
mCameraAccessController.startObservingIfNeeded();
}
- if (!Flags.streamPermissions()) {
- mPermissionDialogComponent = getPermissionDialogComponent();
- } else {
- mPermissionDialogComponent = null;
- }
mVirtualCameraController = virtualCameraController;
try {
token.linkToDeath(this, 0);
@@ -1298,7 +1293,6 @@
/* crossTaskNavigationExemptions= */crossTaskNavigationAllowedByDefault
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
- mPermissionDialogComponent,
mActivityListenerAdapter,
this::onActivityBlocked,
this::onSecureWindowShown,
@@ -1310,13 +1304,6 @@
return gwpc;
}
- private ComponentName getPermissionDialogComponent() {
- Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
- PackageManager packageManager = mContext.getPackageManager();
- intent.setPackage(packageManager.getPermissionControllerPackageName());
- return intent.resolveActivity(packageManager);
- }
-
int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback, String packageName) {
GenericWindowPolicyController gwpc;
@@ -1328,7 +1315,7 @@
this, gwpc, packageName);
gwpc.setDisplayId(displayId, /* isMirrorDisplay= */ Flags.interactiveScreenMirror()
&& mDisplayManagerInternal.getDisplayIdToMirror(displayId)
- != Display.INVALID_DISPLAY);
+ != Display.INVALID_DISPLAY);
boolean showPointer;
synchronized (mVirtualDeviceLock) {
@@ -1359,11 +1346,9 @@
Binder.restoreCallingIdentity(token);
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_display_created_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_display_created_count",
+ mAttributionSource.getUid());
return displayId;
}
@@ -1382,7 +1367,8 @@
}
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
- private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
+ private void onActivityBlocked(int displayId, ActivityInfo activityInfo,
+ IntentSender intentSender) {
Intent intent = BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName());
if (shouldShowBlockedActivityDialog(
activityInfo.getComponentName(), intent.getComponent())) {
@@ -1398,7 +1384,8 @@
displayId,
activityInfo.getComponentName(),
UserHandle.getUserHandleForUid(
- activityInfo.applicationInfo.uid).getIdentifier());
+ activityInfo.applicationInfo.uid).getIdentifier(),
+ intentSender);
}
}
@@ -1431,11 +1418,9 @@
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_secure_window_blocked_count",
- mAttributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_secure_window_blocked_count",
+ mAttributionSource.getUid());
}
}
@@ -1638,8 +1623,8 @@
// Explicitly match the actions because the intent filter will match any intent
// without an explicit action. If the intent has no action, then require that there
// are no actions specified in the filter either.
- boolean explicitActionMatch = !intentInterceptionActionMatchingFix()
- || intent.getAction() != null || intentFilter.countActions() == 0;
+ boolean explicitActionMatch =
+ intent.getAction() != null || intentFilter.countActions() == 0;
if (explicitActionMatch && intentFilter.match(
intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(),
intent.getCategories(), TAG) >= 0) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 1be1d2b..3cd1ca4 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -474,9 +474,7 @@
deviceId,
cameraAccessController, mPendingTrampolineCallback, activityListener,
soundEffectListener, runningAppsChangedCallback, params);
- if (Flags.expressMetrics()) {
- Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");
- }
+ Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");
synchronized (mVirtualDeviceManagerLock) {
if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) {
@@ -500,11 +498,9 @@
}
});
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_devices_created_with_uid_count",
- attributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_devices_created_with_uid_count",
+ attributionSource.getUid());
return virtualDevice;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
index 4bffb76..d3b3945 100644
--- a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
+++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
@@ -77,11 +77,9 @@
mAudioPlaybackDetector = new AudioPlaybackDetector(context);
mAudioRecordingDetector = new AudioRecordingDetector(context);
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_audio_created_count",
- attributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_audio_created_count",
+ attributionSource.getUid());
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 62efafb..7b81ef3 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -102,11 +102,9 @@
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
- Counter.logIncrementWithUid(
- "virtual_devices.value_virtual_camera_created_count",
- attributionSource.getUid());
- }
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_camera_created_count",
+ attributionSource.getUid());
}
/**
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 363c1d8..89d7961 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -30,6 +30,18 @@
],
}
+filegroup {
+ name: "services.core-aidl-sources",
+ srcs: [
+ ":dumpstate_aidl",
+ ":framework_native_aidl",
+ ":gsiservice_aidl",
+ ":installd_aidl",
+ ":storaged_aidl",
+ ":vold_aidl",
+ ],
+}
+
java_library_static {
name: "services-config-update",
srcs: [
@@ -120,14 +132,9 @@
":android.hardware.tv.hdmi.earc-V1-java-source",
":statslog-art-java-gen",
":statslog-contexthub-java-gen",
+ ":services.core-aidl-sources",
":services.core-sources",
":services.core.protologsrc",
- ":dumpstate_aidl",
- ":framework_native_aidl",
- ":gsiservice_aidl",
- ":installd_aidl",
- ":storaged_aidl",
- ":vold_aidl",
":platform-compat-config",
":platform-compat-overrides",
":display-device-config",
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 1ccc48d..2de4482 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -157,7 +157,7 @@
private int mLastMaxChargingVoltage;
private int mLastChargeCounter;
private int mLastBatteryCycleCount;
- private int mLastCharingState;
+ private int mLastChargingState;
/**
* The last seen charging policy. This requires the
* {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -555,7 +555,7 @@
|| mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
|| mInvalidCharger != mLastInvalidCharger
|| mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
- || mHealthInfo.chargingState != mLastCharingState)) {
+ || mHealthInfo.chargingState != mLastChargingState)) {
if (mPlugType != mLastPlugType) {
if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -738,7 +738,7 @@
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
- mLastCharingState = mHealthInfo.chargingState;
+ mLastChargingState = mHealthInfo.chargingState;
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 33cf842..79e09d7 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2229,7 +2229,7 @@
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)
- && idMatch(r, subId, phoneId)) {
+ && idMatchRelaxed(r, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(preciseState);
} catch (RemoteException ex) {
@@ -3535,6 +3535,10 @@
synchronized (mRecords) {
int phoneId = getPhoneIdFromSubId(subId);
+ if (!validatePhoneId(phoneId)) {
+ loge("Invalid phone ID " + phoneId + " for " + subId);
+ return;
+ }
mCarrierRoamingNtnMode[phoneId] = active;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
@@ -3582,6 +3586,10 @@
synchronized (mRecords) {
int phoneId = getPhoneIdFromSubId(subId);
+ if (!validatePhoneId(phoneId)) {
+ loge("Invalid phone ID " + phoneId + " for " + subId);
+ return;
+ }
mCarrierRoamingNtnEligible[phoneId] = eligible;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cf0befa..9d8f337 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1716,6 +1716,12 @@
*/
@Nullable volatile ContentCaptureManagerInternal mContentCaptureService;
+ /**
+ * The interface to the freezer.
+ */
+ @NonNull
+ private final Freezer mFreezer;
+
/*
* The default duration for the binder heavy hitter auto sampler
*/
@@ -2506,6 +2512,7 @@
@Nullable UserController userController) {
mInjector = injector;
mContext = mInjector.getContext();
+ mFreezer = injector.getFreezer();
mUiContext = null;
mAppErrors = injector.getAppErrors();
mPackageWatchdog = null;
@@ -2555,6 +2562,7 @@
LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY);
mInjector = new Injector(systemContext);
mContext = systemContext;
+ mFreezer = mInjector.getFreezer();
mFactoryTest = FactoryTest.getMode();
mSystemThread = ActivityThread.currentActivityThread();
@@ -18436,6 +18444,23 @@
implements ActivityManagerLocal {
@Override
+ public void addFrozenProcessListener(int pid, @NonNull Executor executor,
+ @NonNull FrozenProcessListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mProcLock) {
+ final ProcessRecord app;
+ synchronized (mPidsSelfLocked) {
+ app = mPidsSelfLocked.get(pid);
+ }
+ if (app != null) {
+ mOomAdjuster.mCachedAppOptimizer.addFrozenProcessListener(app, executor,
+ listener);
+ }
+ }
+ }
+
+ @Override
public List<PendingIntentStats> getPendingIntentStats() {
return mPendingIntentController.dumpPendingIntentStatsForStatsd();
}
@@ -20919,6 +20944,11 @@
public IntentFirewall getIntentFirewall() {
return null;
}
+
+ /** @return the default Freezer. */
+ public Freezer getFreezer() {
+ return new Freezer();
+ }
}
@Override
@@ -21022,7 +21052,7 @@
final long token = Binder.clearCallingIdentity();
try {
- return CachedAppOptimizer.isFreezerSupported();
+ return mFreezer.isFreezerSupported();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -21177,4 +21207,9 @@
void clearPendingTopAppLocked() {
mPendingStartActivityUids.clear();
}
+
+ @NonNull
+ Freezer getFreezer() {
+ return mFreezer;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index bbd4323..0e19347 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1234,8 +1234,7 @@
if (freeze) {
mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc);
} else {
- mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0,
- true);
+ mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppLSP(proc, 0, true);
}
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index ee7033e..415f78a 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -65,7 +65,6 @@
import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerService;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.wm.WindowProcessController;
@@ -1027,7 +1026,8 @@
isBackground &= (userId != profileId);
}
int visibleUserId = getVisibleUserId(userId);
- boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId);
+ boolean isVisibleUser = LocalServices.getService(UserManagerInternal.class)
+ .isVisibleBackgroundFullUser(visibleUserId);
boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0;
if (isBackground && !showBackground && !isVisibleUser) {
@@ -1050,7 +1050,7 @@
mContext.getContentResolver(),
Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
0,
- mService.mUserController.getCurrentUserId()) != 0;
+ visibleUserId) != 0;
final String packageName = proc.info.packageName;
final boolean crashSilenced = mAppsNotReportingCrashes != null
&& mAppsNotReportingCrashes.contains(proc.info.packageName);
@@ -1183,26 +1183,6 @@
}
/**
- * Checks if the given user is a visible background user, which is a full, background user
- * assigned to secondary displays on the devices that have
- * {@link UserManager#isVisibleBackgroundUsersEnabled()
- * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
- * automotive builds, using the display associated with their seats).
- *
- * @see UserManager#isUserVisible()
- */
- private boolean isVisibleBackgroundUser(int userId) {
- if (!UserManager.isVisibleBackgroundUsersEnabled()) {
- return false;
- }
- boolean isForeground = mService.mUserController.getCurrentUserId() == userId;
- boolean isProfile = UserManagerService.getInstance().isProfile(userId);
- boolean isVisible = LocalServices.getService(UserManagerInternal.class)
- .isUserVisible(userId);
- return isVisible && !isForeground && !isProfile;
- }
-
- /**
* Information about a process that is currently marked as bad.
*/
static final class BadProcessInfo {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f936b81..6333159 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1260,7 +1260,14 @@
.setMinConsumedPowerThreshold(minConsumedPowerThreshold)
.build();
bus = getBatteryUsageStats(List.of(query)).get(0);
- return new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
+ final int pullResult =
+ new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data);
+ try {
+ bus.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failure close BatteryUsageStats", e);
+ }
+ return pullResult;
}
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1c4ffbb..8f52f67 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -49,6 +49,7 @@
import android.annotation.IntDef;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.FrozenProcessListener;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
@@ -99,6 +100,7 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.Executor;
public final class CachedAppOptimizer {
@@ -664,6 +666,8 @@
private final ProcessDependencies mProcessDependencies;
private final ProcLocksReader mProcLocksReader;
+ private final Freezer mFreezer;
+
public CachedAppOptimizer(ActivityManagerService am) {
this(am, null, new DefaultProcessDependencies());
}
@@ -680,6 +684,7 @@
mTestCallback = callback;
mSettingsObserver = new SettingsContentObserver();
mProcLocksReader = new ProcLocksReader();
+ mFreezer = mAm.getFreezer();
}
/**
@@ -1050,89 +1055,6 @@
}
/**
- * Informs binder that a process is about to be frozen. If freezer is enabled on a process via
- * this method, this method will synchronously dispatch all pending transactions to the
- * specified pid. This method will not add significant latencies when unfreezing.
- * After freezing binder calls, binder will block all transaction to the frozen pid, and return
- * an error to the sending process.
- *
- * @param pid the target pid for which binder transactions are to be frozen
- * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
- * binder for the specificed pid.
- * @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
- * before giving up.
- *
- * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
- * @return 0 if success, or -EAGAIN indicating there's pending transaction.
- */
- public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
-
- /**
- * Retrieves binder freeze info about a process.
- * @param pid the pid for which binder freeze info is to be retrieved.
- *
- * @throws RuntimeException if the operation could not complete successfully.
- * @return a bit field reporting the binder freeze info for the process.
- */
- private static native int getBinderFreezeInfo(int pid);
-
- /**
- * Returns the path to be checked to verify whether the freezer is supported by this system.
- * @return absolute path to the file
- */
- private static native String getFreezerCheckPath();
-
- /**
- * Check if task_profiles.json includes valid freezer profiles and actions
- * @return false if there are invalid profiles or actions
- */
- private static native boolean isFreezerProfileValid();
-
- /**
- * Determines whether the freezer is supported by this system
- */
- public static boolean isFreezerSupported() {
- boolean supported = false;
- FileReader fr = null;
-
- try {
- String path = getFreezerCheckPath();
- Slog.d(TAG_AM, "Checking cgroup freezer: " + path);
- fr = new FileReader(path);
- char state = (char) fr.read();
-
- if (state == '1' || state == '0') {
- // Also check freezer binder ioctl
- Slog.d(TAG_AM, "Checking binder freezer ioctl");
- getBinderFreezeInfo(Process.myPid());
-
- // Check if task_profiles.json contains invalid profiles
- Slog.d(TAG_AM, "Checking freezer profiles");
- supported = isFreezerProfileValid();
- } else {
- Slog.e(TAG_AM, "Unexpected value in cgroup.freeze");
- }
- } catch (java.io.FileNotFoundException e) {
- Slog.w(TAG_AM, "File cgroup.freeze not present");
- } catch (RuntimeException e) {
- Slog.w(TAG_AM, "Unable to read freezer info");
- } catch (Exception e) {
- Slog.w(TAG_AM, "Unable to read cgroup.freeze: " + e.toString());
- }
-
- if (fr != null) {
- try {
- fr.close();
- } catch (java.io.IOException e) {
- Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
- }
- }
-
- Slog.d(TAG_AM, "Freezer supported: " + supported);
- return supported;
- }
-
- /**
* Reads the flag value from DeviceConfig to determine whether app freezer
* should be enabled, and starts the freeze/compaction thread if needed.
*/
@@ -1146,7 +1068,7 @@
} else if ("enabled".equals(configOverride)
|| DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
- mUseFreezer = isFreezerSupported();
+ mUseFreezer = mFreezer.isFreezerSupported();
updateFreezerDebounceTimeout();
updateFreezerExemptInstPkg();
} else {
@@ -1486,8 +1408,15 @@
}
}
+ /**
+ * Returns true if the app was frozen and became unfrozen, otherwise false.
+ *
+ * Do not call this directly. It will unfreeze a process but it will not send out any
+ * notifications. Instead call unfreezeAppLSP().
+ */
@GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
- void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
+ private boolean unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason,
+ boolean force) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
boolean sticky = opt.isFreezeSticky();
@@ -1498,7 +1427,7 @@
"Skip unfreezing because frozen state is sticky pid=" + pid + " "
+ app.processName);
}
- return;
+ return false;
}
boolean processFreezableChangeReported = false;
if (opt.isPendingFreeze()) {
@@ -1520,7 +1449,7 @@
opt.setFreezerOverride(false);
if (pid == 0 || !opt.isFrozen()) {
- return;
+ return false;
}
// Unfreeze the binder interface first, to avoid transactions triggered by timers fired
@@ -1528,7 +1457,7 @@
boolean processKilled = false;
try {
- int freezeInfo = getBinderFreezeInfo(pid);
+ int freezeInfo = mFreezer.getBinderFreezeInfo(pid);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
Slog.d(TAG_AM, "pid " + pid + " " + app.processName
@@ -1553,7 +1482,7 @@
}
if (processKilled) {
- return;
+ return false;
}
if (!processFreezableChangeReported) {
reportProcessFreezableChangedLocked(app);
@@ -1562,19 +1491,19 @@
long freezeTime = opt.getFreezeUnfreezeTime();
try {
- freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
+ mFreezer.freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
app.killLocked("Unable to unfreeze",
ApplicationExitInfo.REASON_FREEZER,
ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
- return;
+ return false;
}
try {
traceAppFreeze(app.processName, pid, reason);
- Process.setProcessFrozen(pid, app.uid, false);
+ mFreezer.setProcessFrozen(pid, app.uid, false);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(false);
@@ -1592,14 +1521,25 @@
pid,
(int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
new Pair<ProcessRecord, Integer>(app, reason)));
+ return true;
+ }
+ return false;
+ }
+
+ @GuardedBy({"mAm", "mProcLock"})
+ void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
+ final boolean shouldDispatch;
+ synchronized (mFreezerLock) {
+ shouldDispatch = unfreezeAppInternalLSP(app, reason, force);
+ }
+ if (shouldDispatch) {
+ app.mOptRecord.dispatchUnfrozenEvent();
}
}
@GuardedBy({"mAm", "mProcLock"})
void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
- synchronized (mFreezerLock) {
- unfreezeAppInternalLSP(app, reason, false);
- }
+ unfreezeAppLSP(app, reason, false);
}
/**
@@ -1610,14 +1550,15 @@
* @param pid pid of the process to be unfrozen
*/
void unfreezeProcess(int pid, @OomAdjReason int reason) {
+ final ProcessRecord app;
synchronized (mFreezerLock) {
- ProcessRecord app = mFrozenProcesses.get(pid);
+ app = mFrozenProcesses.get(pid);
if (app == null) {
return;
}
Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " + reason);
try {
- freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
+ mFreezer.freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
return;
@@ -1625,11 +1566,12 @@
try {
traceAppFreeze(app.processName, pid, reason);
- Process.setProcessFrozen(pid, app.uid, false);
+ mFreezer.setProcessFrozen(pid, app.uid, false);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
}
}
+ app.mOptRecord.dispatchUnfrozenEvent();
}
/**
@@ -2394,7 +2336,7 @@
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
- if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
+ if (mFreezer.freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
handleBinderFreezerFailure(proc, "outstanding txns");
return;
}
@@ -2413,7 +2355,7 @@
try {
traceAppFreeze(proc.processName, pid, -1);
- Process.setProcessFrozen(pid, proc.uid, true);
+ mFreezer.setProcessFrozen(pid, proc.uid, true);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
opt.setFrozen(true);
opt.setHasCollectedFrozenPSS(false);
@@ -2452,7 +2394,7 @@
try {
// post-check to prevent races
- int freezeInfo = getBinderFreezeInfo(pid);
+ int freezeInfo = mFreezer.getBinderFreezeInfo(pid);
if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
synchronized (mProcLock) {
@@ -2470,6 +2412,7 @@
}
});
}
+ opt.dispatchFrozenEvent();
}
private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
@@ -2620,6 +2563,22 @@
}
/**
+ * Freeze or unfreeze a process. This should only be used for testing.
+ */
+ @VisibleForTesting
+ void forceFreezeForTest(ProcessRecord proc, boolean freeze) {
+ synchronized (mAm) {
+ synchronized (mProcLock) {
+ if (freeze) {
+ forceFreezeAppAsyncLSP(proc);
+ } else {
+ unfreezeAppLSP(proc, UNFREEZE_REASON_NONE, true);
+ }
+ }
+ }
+ }
+
+ /**
* Sending binder transactions to frozen apps most likely indicates there's a bug. Log it and
* kill the frozen apps if they 1) receive sync binder transactions while frozen, or 2) miss
* async binder transactions due to kernel binder buffer running out.
@@ -2660,7 +2619,7 @@
for (int i = 0; i < pids.size(); i++) {
int current = pids.get(i);
try {
- int freezeInfo = getBinderFreezeInfo(current);
+ int freezeInfo = mFreezer.getBinderFreezeInfo(current);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
killProcess(current, "Sync transaction while frozen",
@@ -2716,4 +2675,12 @@
exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
+
+ /**
+ * Register a callback to notify when a process's frozen state changes.
+ */
+ public void addFrozenProcessListener(ProcessRecord app, Executor executor,
+ FrozenProcessListener listener) {
+ app.mOptRecord.addFrozenProcessListener(executor, listener);
+ }
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index afb7bb4..1314521 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1806,10 +1806,12 @@
ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG, cpr);
}
final int userId = UserHandle.getUserId(cpr.uid);
+ boolean removed = false;
// Don't remove from provider map if it doesn't match
// could be a new content provider is starting
if (mProviderMap.getProviderByClass(cpr.name, userId) == cpr) {
mProviderMap.removeProviderByClass(cpr.name, userId);
+ removed = true;
}
String[] names = cpr.info.authority.split(";");
for (int j = 0; j < names.length; j++) {
@@ -1817,8 +1819,12 @@
// could be a new content provider is starting
if (mProviderMap.getProviderByName(names[j], userId) == cpr) {
mProviderMap.removeProviderByName(names[j], userId);
+ removed = true;
}
}
+ if (removed && cpr.proc != null) {
+ cpr.proc.mProviders.removeProvider(cpr.info.name);
+ }
}
for (int i = cpr.connections.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/am/Freezer.java b/services/core/java/com/android/server/am/Freezer.java
new file mode 100644
index 0000000..3b3cf55
--- /dev/null
+++ b/services/core/java/com/android/server/am/Freezer.java
@@ -0,0 +1,115 @@
+/*
+ * 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.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+
+import android.os.Process;
+
+/**
+ * A collection of interfaces to manage the freezer. All access to the freezer goes through an
+ * instance of this class. The class can be overridden for testing.
+ *
+ * Methods may be called without external synchronization. Multiple instances of this class can be
+ * used concurrently.
+ */
+class Freezer {
+
+ /**
+ * Freeze or unfreeze the specified process.
+ *
+ * @param pid Identifier of the process to freeze or unfreeze.
+ * @param uid Identifier of the user the process is running under.
+ * @param frozen Specify whether to free (true) or unfreeze (false).
+ */
+ public void setProcessFrozen(int pid, int uid, boolean frozen) {
+ Process.setProcessFrozen(pid, uid, frozen);
+ }
+
+ /**
+ * Informs binder that a process is about to be frozen. If freezer is enabled on a process via
+ * this method, this method will synchronously dispatch all pending transactions to the
+ * specified pid. This method will not add significant latencies when unfreezing.
+ * After freezing binder calls, binder will block all transaction to the frozen pid, and return
+ * an error to the sending process.
+ *
+ * @param pid the target pid for which binder transactions are to be frozen
+ * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
+ * binder for the specified pid.
+ * @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
+ * before giving up.
+ *
+ * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+ * @return 0 if success, or -EAGAIN indicating there's pending transaction.
+ */
+ public int freezeBinder(int pid, boolean freeze, int timeoutMs) {
+ return nativeFreezeBinder(pid, freeze, timeoutMs);
+ }
+
+ /**
+ * Retrieves binder freeze info about a process.
+ * @param pid the pid for which binder freeze info is to be retrieved.
+ *
+ * @throws RuntimeException if the operation could not complete successfully.
+ * @return a bit field reporting the binder freeze info for the process.
+ */
+ public int getBinderFreezeInfo(int pid) {
+ return nativeGetBinderFreezeInfo(pid);
+ }
+
+ /**
+ * Determines whether the freezer is supported by this system.
+ * @return true if the freezer is supported.
+ */
+ public boolean isFreezerSupported() {
+ return nativeIsFreezerSupported();
+ }
+
+ // Native methods
+
+ /**
+ * Informs binder that a process is about to be frozen. If freezer is enabled on a process via
+ * this method, this method will synchronously dispatch all pending transactions to the
+ * specified pid. This method will not add significant latencies when unfreezing.
+ * After freezing binder calls, binder will block all transaction to the frozen pid, and return
+ * an error to the sending process.
+ *
+ * @param pid the target pid for which binder transactions are to be frozen
+ * @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
+ * binder for the specified pid.
+ * @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
+ * before giving up.
+ *
+ * @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+ * @return 0 if success, or -EAGAIN indicating there's pending transaction.
+ */
+ private static native int nativeFreezeBinder(int pid, boolean freeze, int timeoutMs);
+
+ /**
+ * Retrieves binder freeze info about a process.
+ * @param pid the pid for which binder freeze info is to be retrieved.
+ *
+ * @throws RuntimeException if the operation could not complete successfully.
+ * @return a bit field reporting the binder freeze info for the process.
+ */
+ private static native int nativeGetBinderFreezeInfo(int pid);
+
+ /**
+ * Return 0 if the freezer is supported on this platform and -1 otherwise.
+ */
+ private static native boolean nativeIsFreezerSupported();
+}
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index b517631..d6f04db 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -39,6 +39,7 @@
per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com
per-file CachedAppOptimizer.java = file:/PERFORMANCE_OWNERS
+per-file Freezer.java = file:/PERFORMANCE_OWNERS
# Multiuser
per-file User* = file:/MULTIUSER_OWNERS
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8eef71e..6857b6b 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -19,9 +19,11 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SYSTEM_UID;
@@ -367,17 +369,6 @@
}
/**
- * Return true if the activity options allows PendingIntent to use caller's BAL permission.
- */
- public static boolean isPendingIntentBalAllowedByPermission(
- @Nullable ActivityOptions activityOptions) {
- if (activityOptions == null) {
- return false;
- }
- return activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission();
- }
-
- /**
* Return the {@link BackgroundStartPrivileges} the activity options grant the PendingIntent to
* use caller's BAL permission.
*/
@@ -404,6 +395,8 @@
return BackgroundStartPrivileges.NONE;
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE:
case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
default:
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 53643b8..57a5e3f 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -19,6 +19,8 @@
import android.annotation.IntDef;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManagerInternal.OomAdjReason;
+import android.app.ActivityManagerInternal.FrozenProcessListener;
+import android.util.Pair;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -29,6 +31,8 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
/**
* The state info of app when it's cached, used by the optimizer.
@@ -165,6 +169,12 @@
@GuardedBy("mProcLock")
private long mLastUsedTimeout;
+ /**
+ * The list of callbacks for this process whenever it is frozen or unfrozen.
+ */
+ final CopyOnWriteArrayList<Pair<Executor, FrozenProcessListener>> mFrozenProcessListeners =
+ new CopyOnWriteArrayList<>();
+
@GuardedBy("mProcLock")
long getLastCompactTime() {
return mLastCompactTime;
@@ -386,6 +396,22 @@
mFreezeExempt = exempt;
}
+ void addFrozenProcessListener(Executor executor, FrozenProcessListener listener) {
+ mFrozenProcessListeners.add(new Pair<Executor, FrozenProcessListener>(executor, listener));
+ }
+
+ void dispatchFrozenEvent() {
+ mFrozenProcessListeners.forEach((pair) -> {
+ pair.first.execute(() -> pair.second.onProcessFrozen(mApp.mPid));
+ });
+ }
+
+ void dispatchUnfrozenEvent() {
+ mFrozenProcessListeners.forEach((pair) -> {
+ pair.first.execute(() -> pair.second.onProcessUnfrozen(mApp.mPid));
+ });
+ }
+
ProcessCachedOptimizerRecord(ProcessRecord app) {
mApp = app;
mProcLock = app.mService.mProcLock;
@@ -409,6 +435,10 @@
pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
pw.print(prefix); pw.print("earliestFreezableTimeMs=");
TimeUtils.formatDuration(mEarliestFreezableTimeMillis, nowUptime, pw);
+ if (!mFrozenProcessListeners.isEmpty()) {
+ pw.print(" mFrozenProcessListeners=");
+ mFrozenProcessListeners.forEach((pair) -> pw.print(pair.second + ", "));
+ }
pw.println();
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 726e827..bb0c24b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3005,7 +3005,7 @@
return freezePackageCgroup(packageUID, false);
}
- private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
+ private void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
int packageUID) {
// Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
// Since we're going to kill these, we don't need to unfreze them later.
@@ -3019,7 +3019,7 @@
try {
int rc;
do {
- rc = CachedAppOptimizer.freezeBinder(pid, true, 10 /* timeout_ms */);
+ rc = mService.getFreezer().freezeBinder(pid, true, 10 /* timeout_ms */);
} while (rc == -EAGAIN && nRetries++ < 1);
if (rc != 0) Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + rc);
} catch (RuntimeException e) {
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/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ac43e86..53b04df 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2032,8 +2032,11 @@
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
- stream);
+ boolean enabled = true;
+ if (dev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ enabled = mAvrcpAbsVolSupported;
+ }
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", enabled, stream);
});
}
@@ -4831,6 +4834,20 @@
private void onUpdateContextualVolumes() {
final int streamType = getBluetoothContextualVolumeStream();
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> {
+ boolean enabled = true;
+ if (absDev == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+ enabled = mAvrcpAbsVolSupported;
+ }
+ if (stream != streamType) {
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/"",
+ enabled, streamType);
+ }
+ return streamType;
+ });
+ }
+
final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
final Set<Integer> absVolumeMultiModeCaseDevices =
AudioSystem.intersectionAudioDeviceTypes(
@@ -6450,17 +6467,6 @@
// change of mode may require volume to be re-applied on some devices
onUpdateContextualVolumes();
- synchronized (mCachedAbsVolDrivingStreamsLock) {
- mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> {
- int streamToDriveAbs = getBluetoothContextualVolumeStream();
- if (stream != streamToDriveAbs) {
- mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/
- "", /*enabled*/true, streamToDriveAbs);
- }
- return streamToDriveAbs;
- });
- }
-
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
mDeviceBroker.postSetModeOwner(mode, pid, uid);
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/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 15c8850..b2e95aa 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -16,8 +16,11 @@
}
flag {
- name: "notify_fingerprint_loe"
+ name: "notify_fingerprints_loe"
namespace: "biometrics_framework"
description: "This flag controls whether a notification should be sent to notify user when loss of enrollment happens"
bug: "351036558"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 3b6aeef..77e27ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -108,7 +108,7 @@
}
if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId())
- && Flags.notifyFingerprintLoe()) {
+ && Flags.notifyFingerprintsLoe()) {
handleInvalidBiometricState();
}
}
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/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 5b23364c..969a684 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2158,8 +2158,12 @@
}
if (mBound) {
mBound = false;
- mLogger.log("unbindService for ", this);
- mContext.unbindService(this);
+ try {
+ mLogger.log("unbindService for ", this);
+ mContext.unbindService(this);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink active sync adapter on close()", e);
+ }
try {
mBatteryStats.noteSyncFinish(mEventName, mSyncAdapterUid);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index e242164..e0aa9bf 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -21,11 +21,13 @@
import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
import android.content.AttributionSource;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.os.Binder;
@@ -36,6 +38,7 @@
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserManager;
import android.permission.PermissionManager;
import android.util.AtomicFile;
import android.util.Log;
@@ -271,6 +274,31 @@
throw new IllegalArgumentException("Unknown grammatical gender");
}
+ // TODO(b/356895553): Don't allow profiles and background user to change system
+ // grammaticalinflection
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE)) {
+ // The check is added only for automotive devices. On automotive devices, it is
+ // possible that multiple users are visible simultaneously using visible background
+ // users. In such cases, it is desired that only the current user (not the visible
+ // background user) can change the GrammaticalInflection of the device.
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (userId != currentUser) {
+ Log.w(TAG,
+ "Only current user is allowed to update GrammaticalInflection if "
+ + "visible background users are enabled. Current User"
+ + currentUser + ". Calling User: " + userId);
+ throw new SecurityException("Only current user is allowed to update "
+ + "GrammaticalInflection.");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
final File file = getGrammaticalGenderFile(userId);
synchronized (mLock) {
final AtomicFile atomicFile = new AtomicFile(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/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 5c939bc..600cf7f 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -202,7 +202,7 @@
break;
case STATE_HIDE_IME_EXPLICIT:
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false, userId, statsToken);
+ mService.setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
} else {
mService.hideCurrentInputLocked(windowToken, statsToken,
0 /* flags */, null /* resultReceiver */, reason, userId);
@@ -210,7 +210,7 @@
break;
case STATE_HIDE_IME_NOT_ALWAYS:
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false, userId, statsToken);
+ mService.setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
} else {
mService.hideCurrentInputLocked(windowToken, statsToken,
InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason,
@@ -221,7 +221,7 @@
if (Flags.refactorInsetsController()) {
// This can be triggered by IMMS#startInputOrWindowGainedFocus. We need to
// set the requestedVisibleTypes in InsetsController first, before applying it.
- setImeVisibilityOnFocusedWindowClient(true, userId, statsToken);
+ mService.setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
} else {
mService.showCurrentInputLocked(windowToken, statsToken,
InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN,
@@ -276,19 +276,4 @@
}
return false;
}
-
- @GuardedBy("ImfLock.class")
- private void setImeVisibilityOnFocusedWindowClient(boolean visibility, @UserIdInt int userId,
- @NonNull ImeTracker.Token statsToken) {
- final var userData = mService.getUserData(userId);
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility,
- statsToken);
- } else {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW);
- }
- }
}
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/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 42a99de..b0dff22 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -28,7 +28,6 @@
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString;
import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS;
@@ -59,7 +58,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
-import com.android.server.wm.ImeTargetChangeListener;
import com.android.server.wm.WindowManagerInternal;
import java.io.PrintWriter;
@@ -76,6 +74,9 @@
private static final boolean DEBUG = InputMethodManagerService.DEBUG;
+ @UserIdInt
+ private final int mUserId;
+
private final InputMethodManagerService mService;
private final WindowManagerInternal mWindowManagerInternal;
@@ -184,73 +185,64 @@
*/
private final ImeVisibilityPolicy mPolicy;
- public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service) {
+ public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service,
+ @UserIdInt int userId) {
this(service,
LocalServices.getService(WindowManagerInternal.class),
LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy,
- new ImeVisibilityPolicy());
+ new ImeVisibilityPolicy(), userId);
}
@VisibleForTesting
public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service,
@NonNull Injector injector) {
this(service, injector.getWmService(), injector.getImeValidator(),
- new ImeVisibilityPolicy());
+ new ImeVisibilityPolicy(), injector.getUserId());
}
interface Injector {
- default WindowManagerInternal getWmService() {
- return null;
- }
+ @NonNull
+ WindowManagerInternal getWmService();
- default InputMethodManagerService.ImeDisplayValidator getImeValidator() {
- return null;
- }
+ @NonNull
+ InputMethodManagerService.ImeDisplayValidator getImeValidator();
+
+ @UserIdInt
+ int getUserId();
}
private ImeVisibilityStateComputer(InputMethodManagerService service,
WindowManagerInternal wmService,
InputMethodManagerService.ImeDisplayValidator imeDisplayValidator,
- ImeVisibilityPolicy imePolicy) {
+ ImeVisibilityPolicy imePolicy, @UserIdInt int userId) {
+ mUserId = userId;
mService = service;
mWindowManagerInternal = wmService;
mImeDisplayValidator = imeDisplayValidator;
mPolicy = imePolicy;
- mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
- @Override
- public void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
- // 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) {
- mHasVisibleImeLayeringOverlay = hasOverlay;
- }
- }
+ }
- @Override
- public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed) {
- final boolean visibleAndNotRemoved = visibleRequested && !removed;
- synchronized (ImfLock.class) {
- if (visibleAndNotRemoved) {
- mCurVisibleImeInputTarget = imeInputTarget;
- return;
- }
- if (mHasVisibleImeLayeringOverlay
- && mCurVisibleImeInputTarget == imeInputTarget) {
- final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
- final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
- ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
- mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken,
- new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
- }
- mCurVisibleImeInputTarget = null;
- }
- }
- });
+ @GuardedBy("ImfLock.class")
+ void setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay) {
+ mHasVisibleImeLayeringOverlay = hasVisibleOverlay;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget,
+ boolean visibleAndNotRemoved) {
+ if (visibleAndNotRemoved) {
+ mCurVisibleImeInputTarget = imeInputTarget;
+ return;
+ }
+ if (mHasVisibleImeLayeringOverlay
+ && mCurVisibleImeInputTarget == imeInputTarget) {
+ final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
+ mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken,
+ new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason), mUserId);
+ }
+ mCurVisibleImeInputTarget = null;
}
/**
@@ -631,6 +623,12 @@
return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state));
}
+ @UserIdInt
+ @VisibleForTesting
+ int getUserId() {
+ return mUserId;
+ }
+
@GuardedBy("ImfLock.class")
boolean isInputShown() {
return mInputShown;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 03cbab5..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>
@@ -463,7 +459,8 @@
// should now try to restart the service for us.
mLastBindTime = SystemClock.uptimeMillis();
clearCurMethodAndSessions();
- mService.mVisibilityStateComputer.setInputShown(false);
+ final var userData = mService.getUserData(mUserId);
+ userData.mVisibilityStateComputer.setInputShown(false);
mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
}
}
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 61d8e0b..8afbd56 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,10 +49,12 @@
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;
@@ -81,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;
@@ -273,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";
@@ -303,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
@@ -356,7 +330,7 @@
@UserIdInt
@BinderThread
private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) {
- return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
+ return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId;
}
/**
@@ -369,7 +343,7 @@
@UserIdInt
private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
return mConcurrentMultiUserModeEnabled
- ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+ ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId;
}
/**
@@ -385,7 +359,7 @@
final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
return mUserManagerInternal.getUserAssignedToDisplay(displayId);
}
- return mCurrentUserId;
+ return mCurrentImeUserId;
}
final Context mContext;
@@ -396,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
@@ -419,11 +406,6 @@
private final InputMethodMenuControllerNew mMenuControllerNew;
@GuardedBy("ImfLock.class")
- @MultiUserUnawareField
- @NonNull
- final ImeVisibilityStateComputer mVisibilityStateComputer;
-
- @GuardedBy("ImfLock.class")
@SharedByAllUsersField
@NonNull
private final DefaultImeVisibilityApplier mVisibilityApplier;
@@ -571,7 +553,7 @@
@GuardedBy("ImfLock.class")
@Nullable
IInputMethodInvoker getCurMethodLocked() {
- return getInputMethodBindingController(mCurrentUserId).getCurMethod();
+ return getInputMethodBindingController(mCurrentImeUserId).getCurMethod();
}
/**
@@ -617,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);
}
}
@@ -628,10 +610,11 @@
final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, userId);
- mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
- accessibilitySoftKeyboardSetting);
final var userData = getUserData(userId);
- if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ visibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
+ accessibilitySoftKeyboardSetting);
+ if (visibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId);
} else if (isShowRequestedForCurrentWindow(userId)) {
@@ -678,22 +661,18 @@
}
// sender userId can be a real user ID or USER_ALL.
final int senderUserId = pendingResult.getSendingUserId();
- if (senderUserId != UserHandle.USER_ALL) {
- synchronized (ImfLock.class) {
- if (senderUserId != mCurrentUserId) {
- // A background user is trying to hide the dialog. Ignore.
- return;
- }
+ synchronized (ImfLock.class) {
+ if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentImeUserId) {
+ // A background user is trying to hide the dialog. Ignore.
+ return;
}
- }
- if (mNewInputMethodSwitcherMenuEnabled) {
- synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(senderUserId);
- mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(),
- senderUserId);
+ final int userId = mCurrentImeUserId;
+ if (Flags.imeSwitcherRevamp()) {
+ final var bindingController = getInputMethodBindingController(userId);
+ mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
+ } else {
+ mMenuController.hideInputMethodMenuLocked(userId);
}
- } else {
- mMenuController.hideInputMethodMenu(senderUserId);
}
} else {
Slog.w(TAG, "Unexpected intent " + intent);
@@ -717,13 +696,19 @@
return;
}
for (int userId : mUserManagerInternal.getUserIds()) {
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
-
+ // Does InputMethodInfo really have data dependency on system locale?
+ // TODO(b/356679261): Check if we really need to update RawInputMethodInfo here.
+ {
+ final var userData = getUserData(userId);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final var rawMethodMap = queryRawInputMethodServiceMap(mContext, userId);
+ userData.mRawInputMethodMap.set(rawMethodMap);
+ final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap,
+ DirectBootAwareness.AUTO,
+ userData.mIsUnlockingOrUnlocked.get());
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext, userId);
@@ -797,66 +782,72 @@
private void onFinishPackageChangesInternal() {
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).
- final var newMethodMapWithoutAdditionalSubtypes =
- queryInputMethodServicesInternal(mContext, userId,
- AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO)
- .getMethodMap();
+ 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 AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(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 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 AdditionalSubtypeMap newAdditionalSubtypeMap =
- additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
- final boolean additionalSubtypeChanged =
- (newAdditionalSubtypeMap != additionalSubtypeMap);
- if (additionalSubtypeChanged) {
- AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
- settings.getMethodMap());
- }
-
- final var newMethodMap = newMethodMapWithoutAdditionalSubtypes
- .applyAdditionalSubtypes(newAdditionalSubtypeMap);
-
- 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;
@@ -996,6 +987,8 @@
Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
ioThread.start();
+ SecureSettingsWrapper.setContentResolver(context.getContentResolver());
+
return new InputMethodManagerService(context,
shouldEnableConcurrentMultiUserMode(context), thread.getLooper(),
Handler.createAsync(ioThread.getLooper()),
@@ -1054,6 +1047,7 @@
public void onUserRemoved(UserInfo user) {
// Called directly from UserManagerService. Do not block the calling thread.
final int userId = user.id;
+ SecureSettingsWrapper.onUserRemoved(userId);
AdditionalSubtypeMapRepository.remove(userId);
InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
@@ -1063,11 +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 settings = queryInputMethodServicesInternal(mService.mContext, userId,
- AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
synchronized (ImfLock.class) {
if (!mService.mSystemReady) {
return;
@@ -1105,19 +1104,25 @@
for (int userId : userIds) {
Slog.d(TAG, "Start initialization for user=" + userId);
+ final var userData = mService.getUserData(userId);
+
AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
- context, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO).getMethodMap();
- InputMethodSettingsRepository.put(userId,
- InputMethodSettings.create(settings, 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, unlocked);
+
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
final int profileParentId = userManagerInternal.getProfileParentId(userId);
final boolean value =
InputMethodDrawsNavBarResourceMonitor.evaluate(context,
profileParentId);
- final var userData = mService.getUserData(userId);
userData.mImeDrawsNavBar.set(value);
userData.mBackgroundLoadLatch.countDown();
@@ -1130,15 +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 additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
- mService.mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO).getMethodMap();
- InputMethodSettingsRepository.put(userId,
- InputMethodSettings.create(settings, 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));
}
}
@@ -1174,7 +1183,6 @@
mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled;
mContext = context;
mRes = context.getResources();
- SecureSettingsWrapper.onStart(mContext);
mHandler = Handler.createAsync(uiLooper, this);
mIoHandler = ioHandler;
@@ -1189,22 +1197,23 @@
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
- mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
mShowOngoingImeSwitcherForPhones = false;
- mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
- @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
+ mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId();
+ final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
InputMethodManagerService.this);
+ final IntFunction<ImeVisibilityStateComputer> visibilityStateComputerFactory =
+ userId -> new ImeVisibilityStateComputer(InputMethodManagerService.this,
+ userId);
mUserDataRepository = new UserDataRepository(
bindingControllerForTesting != null ? bindingControllerForTesting
- : bindingControllerFactory);
+ : bindingControllerFactory, visibilityStateComputerFactory);
mMenuController = new InputMethodMenuController(this);
- mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
+ mMenuControllerNew = Flags.imeSwitcherRevamp()
? new InputMethodMenuControllerNew() : null;
- mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
mClientController = new ClientController(mPackageManagerInternal);
@@ -1234,8 +1243,9 @@
}
@GuardedBy("ImfLock.class")
- private void onUpdateEditorToolTypeLocked(@MotionEvent.ToolType int toolType) {
- final IInputMethodInvoker curMethod = getCurMethodLocked();
+ private void onUpdateEditorToolTypeLocked(@MotionEvent.ToolType int toolType,
+ @UserIdInt int userId) {
+ final var curMethod = getInputMethodBindingController(userId).getCurMethod();
if (curMethod != null) {
curMethod.updateEditorToolType(toolType);
}
@@ -1270,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
@@ -1285,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);
@@ -1310,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);
@@ -1336,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),
@@ -1399,13 +1420,13 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mCurrentUserId;
+ final int currentImeUserId = mCurrentImeUserId;
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
- hideStatusBarIconLocked();
- 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) {
@@ -1475,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
@@ -1606,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
@@ -1634,15 +1634,11 @@
private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
- final InputMethodSettings settings;
- if (directBootAwareness == DirectBootAwareness.AUTO) {
- settings = InputMethodSettingsRepository.get(userId);
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- directBootAwareness);
- }
+ final var userData = getUserData(userId);
+ final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ AdditionalSubtypeMapRepository.get(userId), directBootAwareness,
+ userData.mIsUnlockingOrUnlocked.get());
+ final var settings = InputMethodSettings.create(methodMap, userId);
// Create a copy.
final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList());
// filter caller's access to input methods
@@ -1816,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);
@@ -1832,10 +1828,11 @@
@GuardedBy("ImfLock.class")
void onUnbindCurrentMethodByReset(@UserIdInt int userId) {
final var userData = getUserData(userId);
- final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final ImeTargetWindowState winState = visibilityStateComputer.getWindowStateOrNull(
userData.mImeBindingState.mFocusedWindow);
if (winState != null && !winState.isRequestedImeVisible()
- && !mVisibilityStateComputer.isInputShown()) {
+ && !visibilityStateComputer.isInputShown()) {
// Normally, the focus window will apply the IME visibility state to
// WindowManager when the IME has applied it. But it would be too late when
// switching IMEs in between different users. (Since the focused IME will
@@ -1854,8 +1851,8 @@
@GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
final var userData = getUserData(userId);
- // TODO(b/349904272): Make mVisibilityStateComputer multi-user aware
- final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final ImeTargetWindowState state = visibilityStateComputer.getWindowStateOrNull(
userData.mImeBindingState.mFocusedWindow);
return state != null && state.isRequestedImeVisible();
}
@@ -1919,8 +1916,9 @@
: createStatsTokenForFocusedClient(true /* show */,
SoftInputShowHideReason.ATTACH_NEW_INPUT, userId);
userData.mCurStatsToken = null;
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, statsToken,
- mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
+ visibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT,
userId);
}
@@ -2001,17 +1999,18 @@
final int userId = bindingController.getUserId();
final var userData = getUserData(userId);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
- ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
+ ImeTargetWindowState winState = visibilityStateComputer.getWindowStateOrNull(
userData.mImeBindingState.mFocusedWindow);
if (winState == null) {
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
final int csDisplayId = cs.mSelfReportedDisplayId;
bindingController.setDisplayIdToShowIme(
- mVisibilityStateComputer.computeImeDisplayId(winState, csDisplayId));
+ visibilityStateComputer.computeImeDisplayId(winState, csDisplayId));
// Potentially override the selected input method if the new display belongs to a virtual
// device with a custom IME.
@@ -2019,14 +2018,14 @@
final String deviceMethodId = computeCurrentDeviceMethodIdLocked(
bindingController.getUserId(), selectedMethodId);
if (deviceMethodId == null) {
- mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+ 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;
}
- if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
+ if (visibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
return InputBindResult.NO_IME;
@@ -2546,53 +2545,54 @@
userData.mEnabledAccessibilitySessions.clear();
scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
}
- hideStatusBarIconLocked();
+ hideStatusBarIconLocked(userId);
getUserData(userId).mInFullscreenMode = false;
mWindowManagerInternal.setDismissImeOnBackKeyPressed(false);
scheduleResetStylusHandwriting();
}
@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) {
- 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();
- } 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);
}
}
}
@GuardedBy("ImfLock.class")
- private void hideStatusBarIconLocked() {
+ 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 != mCurrentImeUserId) {
+ return;
+ }
if (mStatusBarManagerInternal != null) {
mStatusBarManagerInternal.setIconVisibility(mSlotIme, false);
}
@@ -2622,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) {
@@ -2638,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
@@ -2654,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());
@@ -2722,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) {
@@ -2763,18 +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);
- }
- mVisibilityStateComputer.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")
@@ -2798,6 +2788,11 @@
@GuardedBy("ImfLock.class")
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 != mCurrentImeUserId) {
+ return;
+ }
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
final var curToken = bindingController.getCurToken();
@@ -2808,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
@@ -2816,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) {
@@ -2830,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
@@ -2852,7 +2847,7 @@
@GuardedBy("ImfLock.class")
void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
- if (!mNewInputMethodSwitcherMenuEnabled) {
+ if (!Flags.imeSwitcherRevamp()) {
mMenuController.updateKeyboardFromSettingsLocked(userId);
}
}
@@ -2920,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);
@@ -2943,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) {
@@ -2970,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(),
@@ -3015,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().
@@ -3036,11 +3040,14 @@
}
@GuardedBy("ImfLock.class")
- private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver) {
+ private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver,
+ @UserIdInt int userId) {
if (resultReceiver == null) {
return;
}
- final boolean isInputShown = mVisibilityStateComputer.isInputShown();
+ final var userData = getUserData(userId);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final boolean isInputShown = visibilityStateComputer.isInputShown();
resultReceiver.send(isInputShown
? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
@@ -3055,12 +3062,15 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
+ final int uid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(uid);
+ final int userId = resolveImeUserIdLocked(callingUserId);
final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
- lastClickToolType, resultReceiver, reason);
+ lastClickToolType, resultReceiver, reason, uid, userId);
// When ZeroJankProxy is enabled, the app has already received "true" as the return
// value, and expect "resultReceiver" to be notified later. See b/327751155.
if (!result && Flags.useZeroJankProxy()) {
- sendResultReceiverFailureLocked(resultReceiver);
+ sendResultReceiverFailureLocked(resultReceiver, userId);
}
return result; // ignored when ZeroJankProxy is enabled.
}
@@ -3070,10 +3080,7 @@
private boolean showSoftInputLocked(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
int lastClickToolType, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- final int userId = resolveImeUserIdLocked(callingUserId);
+ @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId) {
if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken,
userId)) {
ImeTracker.forLogging().onFailed(
@@ -3086,12 +3093,9 @@
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
if (Flags.refactorInsetsController()) {
- boolean wasVisible = mVisibilityStateComputer.isInputShown();
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(true, statsToken);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ boolean wasVisible = visibilityStateComputer.isInputShown();
+ if (setImeVisibilityOnFocusedWindowClient(false, userData, statsToken)) {
if (resultReceiver != null) {
resultReceiver.send(
wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
@@ -3424,7 +3428,9 @@
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
@MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
- if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
+ final var userData = getUserData(userId);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ if (!visibilityStateComputer.onImeShowFlags(statsToken, flags)) {
return false;
}
@@ -3434,10 +3440,9 @@
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
- mVisibilityStateComputer.requestImeVisibility(windowToken, true);
+ visibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
- final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
bindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
@@ -3456,14 +3461,14 @@
userData.mCurStatsToken = null;
if (Flags.useHandwritingListenerForTooltype()) {
- maybeReportToolType();
+ maybeReportToolType(userId);
} else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
- onUpdateEditorToolTypeLocked(lastClickToolType);
+ onUpdateEditorToolTypeLocked(lastClickToolType, userId);
}
mVisibilityApplier.performShowIme(windowToken, statsToken,
- mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
+ visibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
resultReceiver, reason, userId);
- mVisibilityStateComputer.setInputShown(true);
+ visibilityStateComputer.setInputShown(true);
return true;
} else {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3473,7 +3478,8 @@
}
@GuardedBy("ImfLock.class")
- private void maybeReportToolType() {
+ private void maybeReportToolType(@UserIdInt int userId) {
+ // TODO(b/356638981): This needs to be compatible with visible background users.
int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId();
final InputManager im = mContext.getSystemService(InputManager.class);
if (im == null) {
@@ -3492,7 +3498,7 @@
// other toolTypes are irrelevant and reported as unknown.
toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
}
- onUpdateEditorToolTypeLocked(toolType);
+ onUpdateEditorToolTypeLocked(toolType, userId);
}
@Override
@@ -3502,12 +3508,15 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
+ final int uid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(uid);
+ final int userId = resolveImeUserIdLocked(callingUserId);
final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
- resultReceiver, reason);
+ resultReceiver, reason, uid, userId);
// When ZeroJankProxy is enabled, the app has already received "true" as the return
// value, and expect "resultReceiver" to be notified later. See b/327751155.
if (!result && Flags.useZeroJankProxy()) {
- sendResultReceiverFailureLocked(resultReceiver);
+ sendResultReceiverFailureLocked(resultReceiver, userId);
}
return result; // ignored when ZeroJankProxy is enabled.
}
@@ -3516,12 +3525,12 @@
@GuardedBy("ImfLock.class")
private boolean hideSoftInputLocked(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
- final int userId = resolveImeUserIdLocked(callingUserId);
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
+ int uid, @UserIdInt int userId) {
+ final var userData = getUserData(userId);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
- if (mVisibilityStateComputer.isInputShown()) {
+ if (visibilityStateComputer.isInputShown()) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
} else {
@@ -3530,19 +3539,14 @@
}
return false;
}
- final var userData = getUserData(userId);
final long ident = Binder.clearCallingIdentity();
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
if (Flags.refactorInsetsController()) {
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- boolean wasVisible = mVisibilityStateComputer.isInputShown();
- // TODO add windowToken to interface
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false, statsToken);
+ boolean wasVisible = visibilityStateComputer.isInputShown();
+ // TODO add windowToken to interface
+ if (setImeVisibilityOnFocusedWindowClient(false, userData, statsToken)) {
if (resultReceiver != null) {
resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
@@ -3587,7 +3591,8 @@
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
- if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ if (!visibilityStateComputer.canHideIme(statsToken, flags)) {
return false;
}
@@ -3601,10 +3606,10 @@
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
- && (mVisibilityStateComputer.isInputShown()
+ && (visibilityStateComputer.isInputShown()
|| (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
- mVisibilityStateComputer.requestImeVisibility(windowToken, false);
+ visibilityStateComputer.requestImeVisibility(windowToken, false);
if (shouldHideSoftInput) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
@@ -3617,7 +3622,7 @@
ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
}
bindingController.setCurrentMethodNotVisible();
- mVisibilityStateComputer.clearImeShowFlags();
+ visibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3711,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);
@@ -3750,16 +3755,17 @@
// mShowForced flag when the next client's targetSdkVersion is T or higher.
final boolean shouldClearFlag =
mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
- final boolean showForced = mVisibilityStateComputer.mShowForced;
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final boolean showForced = visibilityStateComputer.mShowForced;
if (userData.mImeBindingState.mFocusedWindow != windowToken
&& showForced && shouldClearFlag) {
- mVisibilityStateComputer.mShowForced = false;
+ visibilityStateComputer.mShowForced = false;
}
// 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.
@@ -3845,7 +3851,8 @@
final ImeTargetWindowState windowState = new ImeTargetWindowState(
softInputMode, windowFlags, !sameWindowFocused, isTextEditor,
startInputByWinGainedFocus, toolType);
- mVisibilityStateComputer.setWindowState(windowToken, windowState);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ visibilityStateComputer.setWindowState(windowToken, windowState);
if (sameWindowFocused && isTextEditor) {
if (DEBUG) {
@@ -3876,7 +3883,7 @@
boolean didStart = false;
InputBindResult res = null;
- final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState,
+ final ImeVisibilityResult imeVisRes = visibilityStateComputer.computeState(windowState,
isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags));
if (imeVisRes != null) {
boolean isShow = false;
@@ -4018,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();
}
@@ -4027,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.
*/
@@ -4039,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());
}
}
@@ -4064,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})
@@ -4101,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);
}
}
@@ -4115,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);
@@ -4139,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();
@@ -4286,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
@@ -4340,13 +4285,13 @@
+ subtype.getLocale() + ", " + subtype.getMode());
}
}
+ final var userData = getUserData(userId);
synchronized (ImfLock.class) {
if (!mSystemReady) {
return;
}
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final boolean isCurrentUser = (mCurrentUserId == userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
@@ -4355,14 +4300,12 @@
settings.getMethodMap());
final long ident = Binder.clearCallingIdentity();
try {
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(
- mContext, userId, AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
+ final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap(
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO,
+ userData.mIsUnlockingOrUnlocked.get());
+ final var newSettings = InputMethodSettings.create(methodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
- if (isCurrentUser) {
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
- userId);
- }
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4390,17 +4333,14 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (ImfLock.class) {
- final boolean currentUser = (mCurrentUserId == userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
- if (currentUser) {
- // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
- final var userData = getUserData(userId);
- userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr();
- updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
- }
+ // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
+ final var userData = getUserData(userId);
+ userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr();
+ updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4513,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();
@@ -4702,23 +4642,24 @@
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;
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId());
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient));
userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(
- mVisibilityStateComputer.getLastImeTargetWindow()));
+ visibilityStateComputer.getLastImeTargetWindow()));
proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
userData.mImeBindingState.mFocusedWindowSoftInputMode));
if (userData.mCurEditorInfo != null) {
userData.mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
proto.write(CUR_ID, bindingController.getCurId());
- mVisibilityStateComputer.dumpDebug(proto, fieldId);
+ visibilityStateComputer.dumpDebug(proto, fieldId);
proto.write(IN_FULLSCREEN_MODE, userData.mInFullscreenMode);
proto.write(CUR_TOKEN, Objects.toString(bindingController.getCurToken()));
proto.write(CUR_TOKEN_DISPLAY_ID, bindingController.getCurTokenDisplayId());
@@ -4728,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());
}
@@ -4738,94 +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 IBinder requestToken = mVisibilityStateComputer.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);
}
@@ -4838,9 +4746,10 @@
void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken,
@SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken,
@UserIdInt int userId) {
- final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken,
- userId);
final var userData = getUserData(userId);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(requestImeToken,
+ userId);
final var bindingController = userData.mBindingController;
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
@@ -4859,81 +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
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false, statsToken);
- }
- } else {
- hideCurrentInputLocked(mVisibilityStateComputer.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(false, statsToken);
- // TODO we will loose the flags here
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(true, statsToken);
- }
- } else {
- showCurrentInputLocked(mVisibilityStateComputer.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);
}
}
@@ -4945,8 +4809,8 @@
@GuardedBy("ImfLock.class")
void onApplyImeVisibilityFromComputerLocked(IBinder windowToken,
- @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) {
- final int userId = resolveImeUserIdFromWindowLocked(windowToken);
+ @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result,
+ @UserIdInt int userId) {
mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
result.getReason(), userId);
}
@@ -5012,8 +4876,11 @@
// This is undocumented so far, but IMM#showInputMethodPicker() has been
// implemented so that auxiliary subtypes will be excluded when the soft
// keyboard is invisible.
- case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO ->
- showAuxSubtypes = mVisibilityStateComputer.isInputShown();
+ case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO -> {
+ final var userData = getUserData(userId);
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ showAuxSubtypes = visibilityStateComputer.isInputShown();
+ }
case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES ->
showAuxSubtypes = true;
case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES ->
@@ -5027,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(
@@ -5041,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);
}
}
@@ -5080,13 +4948,8 @@
final int userId = resolveImeUserIdFromDisplayIdLocked(originatingDisplayId);
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false,
- null /* TODO(b329229469) check statsToken */);
- }
+ setImeVisibilityOnFocusedWindowClient(false, userData,
+ null /* TODO(b329229469) check statsToken */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
@@ -5098,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
@@ -5136,7 +4999,7 @@
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
- if (!mNewInputMethodSwitcherMenuEnabled) {
+ if (!Flags.imeSwitcherRevamp()) {
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
}
synchronized (ImfLock.class) {
@@ -5164,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()) {
@@ -5238,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();
}
@@ -5246,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;
@@ -5261,7 +5127,8 @@
bindingController.getCurMethodUid())) {
// Handle IME visibility when interactive changed before finishing the input to
// ensure we preserve the last state as possible.
- final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ final ImeVisibilityResult imeVisRes = visibilityStateComputer.onInteractiveChanged(
userData.mImeBindingState.mFocusedWindow, interactive);
if (imeVisRes != null) {
// Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
@@ -5296,31 +5163,15 @@
}
@NonNull
- static InputMethodSettings queryInputMethodServicesInternal(Context context,
- @UserIdInt int userId, @NonNull AdditionalSubtypeMap additionalSubtypeMap,
- @DirectBootAwareness int directBootAwareness) {
+ static RawInputMethodMap queryRawInputMethodServiceMap(Context context, @UserIdInt int userId) {
final Context userAwareContext = context.getUserId() == userId
? context
: context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
- final int directBootAwarenessFlags;
- switch (directBootAwareness) {
- case DirectBootAwareness.ANY:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- break;
- case DirectBootAwareness.AUTO:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO;
- break;
- default:
- directBootAwarenessFlags = PackageManager.MATCH_DIRECT_BOOT_AUTO;
- Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness
- + ". Falling back to DirectBootAwareness.AUTO");
- break;
- }
final int flags = PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | directBootAwarenessFlags;
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
// Beware that package visibility filtering will be enforced based on the effective calling
// identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid()
@@ -5336,14 +5187,11 @@
final List<String> enabledInputMethodList =
InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId);
- final InputMethodMap methodMap = filterInputMethodServices(
- additionalSubtypeMap, enabledInputMethodList, userAwareContext, services);
- return InputMethodSettings.create(methodMap, userId);
+ return filterInputMethodServices(enabledInputMethodList, userAwareContext, services);
}
@NonNull
- static InputMethodMap filterInputMethodServices(
- @NonNull AdditionalSubtypeMap additionalSubtypeMap,
+ static RawInputMethodMap filterInputMethodServices(
List<String> enabledInputMethodList, Context userAwareContext,
List<ResolveInfo> services) {
final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>();
@@ -5364,7 +5212,7 @@
try {
final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri,
- additionalSubtypeMap.get(imeId));
+ Collections.emptyList());
if (imi.isVrOnly()) {
continue; // Skip VR-only IME, which isn't supported for now.
}
@@ -5387,7 +5235,7 @@
Slog.wtf(TAG, "Unable to load input method " + imeId, e);
}
}
- return InputMethodMap.of(methodMap);
+ return RawInputMethodMap.of(methodMap);
}
@GuardedBy("ImfLock.class")
@@ -5604,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);
@@ -5614,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 {
@@ -5655,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);
}
/**
@@ -5692,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;
}
@@ -5755,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 =
@@ -5775,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;
}
@@ -5853,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);
}
}
@@ -5935,9 +5783,10 @@
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
// the next focused window starting the input connection.
- if (mVisibilityStateComputer.getLastImeTargetWindow()
+ 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 {
@@ -5953,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) {
@@ -6065,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);
}
}
@@ -6220,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:");
@@ -6252,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="
@@ -6270,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());
@@ -6293,7 +6145,7 @@
};
mUserDataRepository.forAllUserData(userDataDump);
- if (mNewInputMethodSwitcherMenuEnabled) {
+ if (Flags.imeSwitcherRevamp()) {
p.println(" menuControllerNew:");
mMenuControllerNew.dump(p, " ");
} else {
@@ -6307,7 +6159,8 @@
method = bindingController.getCurMethod();
p.println(" mCurMethod=" + method);
p.println(" mEnabledSession=" + userData.mEnabledSession);
- mVisibilityStateComputer.dump(pw, " ");
+ final var visibilityStateComputer = userData.mVisibilityStateComputer;
+ visibilityStateComputer.dump(pw, " ");
p.println(" mInFullscreenMode=" + userData.mInFullscreenMode);
p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled);
@@ -6343,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) {
@@ -6574,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()) {
@@ -6620,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;
@@ -6715,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);
@@ -6735,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;
}
@@ -6756,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;
@@ -6771,16 +6646,8 @@
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
- if (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient
- != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false,
- null /* TODO(b329229469) initialize statsToken here? */);
- } else {
- // TODO(b329229469): ImeTracker?
- }
+ setImeVisibilityOnFocusedWindowClient(false, userData,
+ null /* TODO(b329229469) initialize statsToken here? */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
0 /* flags */,
@@ -6819,6 +6686,23 @@
return ShellCommandResult.SUCCESS;
}
+ @GuardedBy("ImfLock.class")
+ boolean setImeVisibilityOnFocusedWindowClient(boolean visible, UserData userData,
+ @NonNull ImeTracker.Token statsToken) {
+ if (Flags.refactorInsetsController()) {
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visible,
+ statsToken);
+ return true;
+ }
+ ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW);
+ }
+ return false;
+ }
+
/**
* Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}.
*
@@ -6912,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;
@@ -6928,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
@@ -6949,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);
}
@@ -6959,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
@@ -6967,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);
}
@@ -6980,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);
}
@@ -6994,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);
}
@@ -7008,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);
}
@@ -7018,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
@@ -7026,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);
}
@@ -7038,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);
}
@@ -7050,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);
}
@@ -7060,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();
@@ -7103,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 35fae18..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);
};
@@ -135,8 +135,8 @@
final RecyclerView recyclerView = contentView
.requireViewById(com.android.internal.R.id.list);
recyclerView.setAdapter(new Adapter(items, selectedIndex, inflater, onClickListener));
- // Scroll to the currently selected IME.
- recyclerView.scrollToPosition(selectedIndex);
+ // Scroll to the currently selected IME. This must run after the recycler view is laid out.
+ recyclerView.post(() -> recyclerView.scrollToPosition(selectedIndex));
// Indicate that the list can be scrolled.
recyclerView.setScrollIndicators(
hasLanguageSettingsButton ? View.SCROLL_INDICATOR_BOTTOM : 0);
@@ -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/RawInputMethodMap.java b/services/core/java/com/android/server/inputmethod/RawInputMethodMap.java
new file mode 100644
index 0000000..4e39a3f
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/RawInputMethodMap.java
@@ -0,0 +1,105 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+
+import java.util.List;
+
+/**
+ * This is quite similar to {@link InputMethodMap} with two major differences.
+ *
+ * <ul>
+ * <li>Additional {@link android.view.inputmethod.InputMethodSubtype} is not included.</li>
+ * <li>Always include direct-boot unaware {@link android.inputmethodservice.InputMethodService}.
+ * </li>
+ * </ul>
+ *
+ * <p>As seen in {@link #toInputMethodMap(AdditionalSubtypeMap, int, boolean)}, you can consider
+ * this is a prototype data where you can always derive {@link InputMethodMap} with
+ * {@link AdditionalSubtypeMap} and a boolean information whether
+ * {@link com.android.server.pm.UserManagerInternal#isUserUnlockingOrUnlocked(int)} returns
+ * {@code true} or not.</p>
+ */
+final class RawInputMethodMap {
+ static final String TAG = "RawInputMethodMap";
+
+ private static final ArrayMap<String, InputMethodInfo> EMPTY_MAP = new ArrayMap<>();
+
+ private final ArrayMap<String, InputMethodInfo> mMap;
+
+ static RawInputMethodMap emptyMap() {
+ return new RawInputMethodMap(EMPTY_MAP);
+ }
+
+ static RawInputMethodMap of(@NonNull ArrayMap<String, InputMethodInfo> map) {
+ return new RawInputMethodMap(map);
+ }
+
+ private RawInputMethodMap(@NonNull ArrayMap<String, InputMethodInfo> map) {
+ mMap = map.isEmpty() ? EMPTY_MAP : new ArrayMap<>(map);
+ }
+
+ @AnyThread
+ @NonNull
+ List<InputMethodInfo> values() {
+ return List.copyOf(mMap.values());
+ }
+
+ @NonNull
+ InputMethodMap toInputMethodMap(@NonNull AdditionalSubtypeMap additionalSubtypeMap,
+ @DirectBootAwareness int directBootAwareness, boolean userUnlocked) {
+ final int size = mMap.size();
+ final var newMap = new ArrayMap<String, InputMethodInfo>(size);
+
+ final boolean requireDirectBootAwareFlag;
+ switch (directBootAwareness) {
+ case DirectBootAwareness.ANY -> requireDirectBootAwareFlag = false;
+ case DirectBootAwareness.AUTO -> requireDirectBootAwareFlag = !userUnlocked;
+ default -> {
+ requireDirectBootAwareFlag = !userUnlocked;
+ Slog.e(TAG, "Unknown directBootAwareness=" + directBootAwareness
+ + ". Falling back to DirectBootAwareness.AUTO");
+ }
+ }
+
+ boolean updated = false;
+ for (int i = 0; i < size; ++i) {
+ final var imeId = mMap.keyAt(i);
+ final var imi = mMap.valueAt(i);
+ if (requireDirectBootAwareFlag && !imi.getServiceInfo().directBootAware) {
+ updated = true;
+ continue;
+ }
+ final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId);
+ if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) {
+ newMap.put(imi.getId(), imi);
+ } else {
+ updated = true;
+ newMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes));
+ }
+ }
+ // If newMap is semantically the same as mMap, we can reuse mMap and discard newMap.
+ return InputMethodMap.of(updated ? newMap : mMap);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index 476888e..3beec09 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -20,10 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.ActivityManagerInternal;
import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -321,30 +318,13 @@
}
/**
- * Called when {@link InputMethodManagerService} is starting.
+ * Called when the system is starting.
*
- * @param context the {@link Context} to be used.
+ * @param contentResolver the {@link ContentResolver} to be used
*/
@AnyThread
- static void onStart(@NonNull Context context) {
- sContentResolver = context.getContentResolver();
-
- final int userId = LocalServices.getService(ActivityManagerInternal.class)
- .getCurrentUserId();
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- putOrGet(userId, createImpl(userManagerInternal, userId));
-
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- synchronized (sMutationLock) {
- sUserMap = sUserMap.cloneWithRemoveOrSelf(user.id);
- }
- }
- }
- );
+ static void setContentResolver(@NonNull ContentResolver contentResolver) {
+ sContentResolver = contentResolver;
}
/**
@@ -394,6 +374,18 @@
}
/**
+ * Called when a user is being removed.
+ *
+ * @param userId the ID of the user whose storage is being removed
+ */
+ @AnyThread
+ static void onUserRemoved(@UserIdInt int userId) {
+ synchronized (sMutationLock) {
+ sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
+ }
+ }
+
+ /**
* Put the given string {@code value} to {@code key}.
*
* @param key a secure settings key.
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 be57321..96da17e 100644
--- a/services/core/java/com/android/server/inputmethod/UserData.java
+++ b/services/core/java/com/android/server/inputmethod/UserData.java
@@ -19,17 +19,21 @@
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;
+import java.util.concurrent.atomic.AtomicReference;
/** Placeholder for all IMMS user specific fields */
final class UserData {
@@ -43,6 +47,17 @@
@NonNull
final CountDownLatch mBackgroundLoadLatch = new CountDownLatch(1);
+ /**
+ * Contains non-null {@link RawInputMethodMap}, which represents the latest collections of
+ * {@link android.view.inputmethod.InputMethodInfo} for both direct-boot aware and unaware IMEs
+ * before taking {@link AdditionalSubtypeMap} into account.
+ *
+ * <p>See {@link RawInputMethodMap} for details on when to use this.</p>
+ */
+ @NonNull
+ final AtomicReference<RawInputMethodMap> mRawInputMethodMap =
+ new AtomicReference<>(RawInputMethodMap.emptyMap());
+
@NonNull
final InputMethodBindingController mBindingController;
@@ -54,6 +69,9 @@
final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
new HardwareKeyboardShortcutController();
+ @NonNull
+ final ImeVisibilityStateComputer mVisibilityStateComputer;
+
/**
* Have we called mCurMethod.bindInput()?
*/
@@ -134,18 +152,39 @@
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.
*/
UserData(@UserIdInt int userId,
- @NonNull InputMethodBindingController bindingController) {
+ @NonNull InputMethodBindingController bindingController,
+ @NonNull ImeVisibilityStateComputer stateComputer) {
mUserId = userId;
mBindingController = bindingController;
+ mVisibilityStateComputer = stateComputer;
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index e3524b1..39f580c 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -30,7 +30,10 @@
@NonNull
private volatile ImmutableSparseArray<UserData> mUserData = ImmutableSparseArray.empty();
+ @NonNull
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
+ @NonNull
+ private final IntFunction<ImeVisibilityStateComputer> mVisibilityStateComputerFactory;
@AnyThread
@NonNull
@@ -42,7 +45,8 @@
}
// Note that the below line can be called concurrently. Here we assume that
// instantiating UserData for the same user multiple times would have no side effect.
- final var newUserData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ final var newUserData = new UserData(userId, mBindingControllerFactory.apply(userId),
+ mVisibilityStateComputerFactory.apply(userId));
synchronized (mMutationLock) {
mUserData = mUserData.cloneWithPutOrSelf(userId, newUserData);
return newUserData;
@@ -54,9 +58,10 @@
mUserData.forEach(consumer);
}
- UserDataRepository(
- @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
+ UserDataRepository(@NonNull IntFunction<InputMethodBindingController> bindingControllerFactory,
+ @NonNull IntFunction<ImeVisibilityStateComputer> visibilityStateComputerFactory) {
mBindingControllerFactory = bindingControllerFactory;
+ mVisibilityStateComputerFactory = visibilityStateComputerFactory;
}
@AnyThread
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/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index cd69eba..2a0b1af 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -717,9 +717,13 @@
}
mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId());
- Log.d(TAG, "Successfully completed reliable message transaction with "
- + "message sequence number: " + transaction.getMessageSequenceNumber()
- + " and result: " + result);
+ Log.d(
+ TAG,
+ "Successfully completed reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + " and result = "
+ + result);
}
/**
@@ -732,15 +736,20 @@
int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
@ContextHubTransaction.Result int result = transaction.onTransact();
if (result == ContextHubTransaction.RESULT_SUCCESS) {
- Log.d(TAG, "Successfully "
- + (numCompletedStartCalls == 0 ? "started" : "retried")
- + " reliable message transaction with message sequence number: "
- + transaction.getMessageSequenceNumber());
+ Log.d(
+ TAG,
+ "Successfully "
+ + (numCompletedStartCalls == 0 ? "started" : "retried")
+ + " reliable message transaction with message sequence number = "
+ + transaction.getMessageSequenceNumber());
} else {
- Log.w(TAG, "Could not start reliable message transaction with "
- + "message sequence number: "
- + transaction.getMessageSequenceNumber()
- + ", result: " + result);
+ Log.w(
+ TAG,
+ "Could not start reliable message transaction with "
+ + "message sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", result = "
+ + result);
}
transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos());
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/location/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index 5df0de8..df45a6e 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -77,7 +77,7 @@
mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
dispatchEmergencyStateChanged();
- } catch (IllegalStateException e) {
+ } catch (IllegalStateException | UnsupportedOperationException e) {
Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e);
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 53b6796..17f6561 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -622,16 +622,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")
@@ -4589,7 +4579,7 @@
@VisibleForTesting
@GuardedBy("mUidRulesFirstLock")
void updateRestrictedModeAllowlistUL() {
- mUidFirewallRestrictedModeRules.clear();
+ final SparseIntArray uidRules = new SparseIntArray();
forEachUid("updateRestrictedModeAllowlist", uid -> {
synchronized (mUidRulesFirstLock) {
final int effectiveBlockedReasons = updateBlockedReasonsForRestrictedModeUL(
@@ -4599,13 +4589,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 +4679,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 +4694,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 +4708,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 +4741,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 +4778,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 +4806,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);
}
}
@@ -5313,16 +5295,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);
}
@@ -6269,18 +6246,8 @@
"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.
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 5a9cf03..b4459cb 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -230,9 +230,17 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
- record -> mPackageManager.checkPermission(
+ record -> {
+ final String category = record.getNotification().category;
+ if (Notification.CATEGORY_ALARM.equals(category)
+ || Notification.CATEGORY_CAR_EMERGENCY.equals(category)
+ || Notification.CATEGORY_CAR_WARNING.equals(category)) {
+ return true;
+ }
+ return mPackageManager.checkPermission(
permission.RECEIVE_EMERGENCY_BROADCAST,
- record.getSbn().getPackageName()) == PERMISSION_GRANTED);
+ record.getSbn().getPackageName()) == PERMISSION_GRANTED;
+ });
return new StrategyAvalanche(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -248,9 +256,17 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
- record -> mPackageManager.checkPermission(
+ record -> {
+ final String category = record.getNotification().category;
+ if (Notification.CATEGORY_ALARM.equals(category)
+ || Notification.CATEGORY_CAR_EMERGENCY.equals(category)
+ || Notification.CATEGORY_CAR_WARNING.equals(category)) {
+ return true;
+ }
+ return mPackageManager.checkPermission(
permission.RECEIVE_EMERGENCY_BROADCAST,
- record.getSbn().getPackageName()) == PERMISSION_GRANTED);
+ record.getSbn().getPackageName()) == PERMISSION_GRANTED;
+ });
}
}
@@ -1178,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 f615ca1d..be6fa14 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -693,13 +693,18 @@
params.appLabel = TextUtils.trimToSize(params.appLabel,
PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
- // Validate installer package name.
+ // Validate requested installer package name.
if (params.installerPackageName != null && !isValidPackageName(
params.installerPackageName)) {
params.installerPackageName = null;
}
- var requestedInstallerPackageName =
+ // Validate installer package name.
+ if (installerPackageName != null && !isValidPackageName(installerPackageName)) {
+ installerPackageName = null;
+ }
+
+ String requestedInstallerPackageName =
params.installerPackageName != null ? params.installerPackageName
: installerPackageName;
@@ -1433,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/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 0e7ce2e..14b0fc8 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -497,6 +497,17 @@
public abstract boolean isUserVisible(@UserIdInt int userId, int displayId);
/**
+ * Checks if the given user is a visible background full user, which is a full background user
+ * assigned to secondary displays on the devices that have
+ * {@link UserManager#isVisibleBackgroundUsersEnabled()
+ * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
+ * automotive builds, using the display associated with their seats).
+ *
+ * @see UserManager#isUserVisible()
+ */
+ public abstract boolean isVisibleBackgroundFullUser(@UserIdInt int userId);
+
+ /**
* Returns the main display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
* user is not assigned to any main display.
*
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index dde9943..57d7d79 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -29,6 +29,7 @@
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
@@ -2104,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) {
@@ -2132,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) {
@@ -4064,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 {
@@ -5442,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);
}
@@ -7927,6 +7963,17 @@
}
@Override
+ public boolean isVisibleBackgroundFullUser(@UserIdInt int userId) {
+ if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+ return false;
+ }
+ boolean isForeground = userId == getCurrentUserId();
+ boolean isProfile = isProfileUnchecked(userId);
+ boolean isVisible = isUserVisible(userId);
+ return isVisible && !isForeground && !isProfile;
+ }
+
+ @Override
public int getMainDisplayAssignedToUser(@UserIdInt int userId) {
return mUserVisibilityMediator.getMainDisplayAssignedToUser(userId);
}
@@ -8028,8 +8075,13 @@
String errorMessage = (message != null ? (message + ": ") : "")
+ restriction + " is enabled.";
Slog.w(LOG_TAG, errorMessage);
- throw new UserManager.CheckedUserOperationException(errorMessage,
+ if (android.multiuser.Flags.showDifferentCreationErrorForUnsupportedDevices()) {
+ throw new UserManager.CheckedUserOperationException(errorMessage,
+ USER_OPERATION_ERROR_USER_RESTRICTED);
+ } else {
+ throw new UserManager.CheckedUserOperationException(errorMessage,
USER_OPERATION_ERROR_UNKNOWN);
+ }
}
}
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 c95be17..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);
@@ -2725,11 +2718,16 @@
@Override
void onLongPress(long eventTime) {
- // Long-press should be triggered only if app doesn't handle it.
- mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_STEM_PRIMARY,
- eventTime,
- () -> stemPrimaryLongPress(eventTime));
+ if (mLongPressOnStemPrimaryBehavior == LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT) {
+ // Long-press to assistant gesture is not overridable by apps.
+ stemPrimaryLongPress(eventTime);
+ } else {
+ // Other long-press actions should be triggered only if app doesn't handle it.
+ mDeferredKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY,
+ eventTime,
+ () -> stemPrimaryLongPress(eventTime));
+ }
}
@Override
@@ -3287,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
@@ -3429,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:
@@ -3438,7 +3427,8 @@
preloadRecentApps();
} else if (!down) {
toggleRecentApps();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
+ notifyKeyboardShortcutTriggered(event,
+ KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
}
}
return true;
@@ -3447,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;
@@ -3460,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;
@@ -3475,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;
}
@@ -3486,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;
@@ -3499,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;
}
@@ -3515,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;
}
}
@@ -3525,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;
}
}
@@ -3535,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;
@@ -3551,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;
}
}
@@ -3563,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;
@@ -3615,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:
@@ -3660,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 =
@@ -3669,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;
}
}
@@ -3682,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:
@@ -3700,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:
@@ -3713,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;
@@ -3732,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;
}
@@ -3764,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:
@@ -3785,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;
@@ -4734,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.
@@ -4755,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);
@@ -4857,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) {
@@ -4867,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,
@@ -4890,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()) {
@@ -4913,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) {
@@ -4924,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;
@@ -4933,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;
@@ -4948,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.
@@ -4993,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;
@@ -5004,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;
@@ -5970,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);
}
}
@@ -6442,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
@@ -6452,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() {
@@ -6646,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/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
index e27f3b2..7496d2d 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
@@ -27,6 +27,7 @@
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import com.android.internal.os.PowerStats;
@@ -73,14 +74,21 @@
private long mDurationMs;
AggregatedPowerStats(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+ this(aggregatedPowerStatsConfig, new SparseBooleanArray());
+ }
+
+ AggregatedPowerStats(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
+ @NonNull SparseBooleanArray enabledComponents) {
mConfig = aggregatedPowerStatsConfig;
List<PowerComponent> configs =
aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs();
mPowerComponentStats = new SparseArray<>(configs.size());
for (int i = 0; i < configs.size(); i++) {
PowerComponent powerComponent = configs.get(i);
- mPowerComponentStats.put(powerComponent.getPowerComponentId(),
- new PowerComponentAggregatedPowerStats(this, powerComponent));
+ if (enabledComponents.get(powerComponent.getPowerComponentId(), true)) {
+ mPowerComponentStats.put(powerComponent.getPowerComponentId(),
+ new PowerComponentAggregatedPowerStats(this, powerComponent));
+ }
}
mGenericPowerComponent = createGenericPowerComponent();
mPowerComponentStats.put(BatteryConsumer.POWER_COMPONENT_ANY, mGenericPowerComponent);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 143b3ff..c878f14 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -16393,6 +16393,10 @@
* Callers will need to wait for the collection to complete on the handler thread.
*/
public void schedulePowerStatsSampleCollection() {
+ if (!mSystemReady) {
+ return;
+ }
+
mCpuPowerStatsCollector.forceSchedule();
mScreenPowerStatsCollector.forceSchedule();
mMobileRadioPowerStatsCollector.forceSchedule();
@@ -16400,6 +16404,7 @@
mBluetoothPowerStatsCollector.forceSchedule();
mCameraPowerStatsCollector.forceSchedule();
mGnssPowerStatsCollector.forceSchedule();
+ mCustomEnergyConsumerPowerStatsCollector.forceSchedule();
}
/**
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index b308f38..d51cfea 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -428,5 +428,6 @@
*/
public void setPowerStatsExporterEnabled(int powerComponentId, boolean enabled) {
mPowerStatsExporterEnabled.put(powerComponentId, enabled);
+ mPowerStatsExporter.setPowerComponentEnabled(powerComponentId, enabled);
}
}
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
index 0273ba6..4bfe442 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
@@ -68,6 +68,16 @@
}
@Override
+ public boolean forceSchedule() {
+ ensureInitialized();
+ boolean success = false;
+ for (int i = 0; i < mCollectors.size(); i++) {
+ success |= mCollectors.get(i).forceSchedule();
+ }
+ return success;
+ }
+
+ @Override
public void collectAndDump(PrintWriter pw) {
ensureInitialized();
for (int i = 0; i < mCollectors.size(); i++) {
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
index ce11fa0..79fbe8e 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -163,10 +163,7 @@
mLayout.setConsumedEnergy(mPowerStats.stats, 0, uJtoUc(energyDelta, averageVoltage));
- for (int i = mPowerStats.uidStats.size() - 1; i >= 0; i--) {
- mLayout.setUidConsumedEnergy(mPowerStats.uidStats.valueAt(i), 0, 0);
- }
-
+ mPowerStats.uidStats.clear();
if (energy != null) {
for (int i = energy.length - 1; i >= 0; i--) {
EnergyConsumerAttribution[] perUid = energy[i].attribution;
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
index 86f515c..081e560 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
@@ -16,7 +16,9 @@
package com.android.server.power.stats;
import android.annotation.NonNull;
+import android.os.BatteryConsumer;
import android.os.BatteryStats;
+import android.util.SparseBooleanArray;
import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.BatteryStatsHistoryIterator;
@@ -32,6 +34,8 @@
private static final long UNINITIALIZED = -1;
private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
private final BatteryStatsHistory mHistory;
+ private final SparseBooleanArray mEnabledComponents =
+ new SparseBooleanArray(BatteryConsumer.POWER_COMPONENT_COUNT + 10);
private AggregatedPowerStats mStats;
private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
@@ -42,8 +46,13 @@
mHistory = history;
}
- AggregatedPowerStatsConfig getConfig() {
- return mAggregatedPowerStatsConfig;
+ void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
+ synchronized (this) {
+ if (mStats != null) {
+ mStats = null;
+ }
+ mEnabledComponents.put(powerComponentId, enabled);
+ }
}
/**
@@ -62,7 +71,7 @@
Consumer<AggregatedPowerStats> consumer) {
synchronized (this) {
if (mStats == null) {
- mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
+ mStats = new AggregatedPowerStats(mAggregatedPowerStatsConfig, mEnabledComponents);
}
mStats.start(startTimeMs);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 5f41090..281faf1 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -216,6 +216,8 @@
PowerStatsLayout layout) {
AggregatedPowerStatsConfig.PowerComponent powerComponent = powerComponentStats.getConfig();
int powerComponentId = powerComponent.getPowerComponentId();
+ boolean isCustomComponent =
+ powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor();
long[] uidStats = new long[descriptor.uidStatsArrayLength];
@@ -223,7 +225,7 @@
boolean breakDownByProcState = batteryUsageStatsBuilder.isProcessStateDataNeeded()
&& powerComponent
.getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked()
- && powerComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ && !isCustomComponent;
ArrayList<Integer> uids = new ArrayList<>();
powerComponentStats.collectUids(uids);
@@ -237,7 +239,7 @@
}
for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) {
- if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) {
+ if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) {
if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) {
continue;
}
@@ -374,4 +376,8 @@
}
return true;
}
+
+ void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
+ mPowerStatsAggregator.setPowerComponentEnabled(powerComponentId, enabled);
+ }
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c21f783..331a594 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1301,7 +1301,7 @@
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{TRANSPORT_BLUETOOTH},
/*slicedByFgbg=*/true, /*slicedByTag=*/false,
/*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
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/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 5d01bc3..d5bea4a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -601,8 +601,8 @@
.getDefaultDisplaySizes().get(orientation);
if (displayForThisOrientation == null) continue;
float sampleSizeForThisOrientation = Math.max(1f, Math.min(
- (float) crop.width() / displayForThisOrientation.x,
- (float) crop.height() / displayForThisOrientation.y));
+ crop.width() / displayForThisOrientation.x,
+ crop.height() / displayForThisOrientation.y));
sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
}
// If the total crop has more width or height than either the max texture size
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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 4085ec9..fb2bf39 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1612,7 +1612,8 @@
int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
if (isAppCompateStateChangedToLetterboxed(state)) {
- positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+ positionToLog = activity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .getLetterboxPositionForLogging();
}
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
packageUid, state, positionToLog);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8a768c0..5c096ec 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1988,8 +1988,8 @@
// Don't move below setOrientation(info.screenOrientation) since it triggers
// getOverrideOrientation that requires having mLetterboxUiController
// initialised.
- mLetterboxUiController = new LetterboxUiController(mWmService, this);
mAppCompatController = new AppCompatController(mWmService, this);
+ mLetterboxUiController = new LetterboxUiController(mWmService, this);
mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
// When the stable configuration is the default behavior, override for the legacy apps
@@ -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();
@@ -8633,8 +8637,8 @@
/**
* Adjusts position of resolved bounds if they don't fill the parent using gravity
* requested in the config or via an ADB command. For more context see {@link
- * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and
- * {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)}
+ * AppCompatReachabilityOverrides#getHorizontalPositionMultiplier(Configuration)} and
+ * {@link AppCompatReachabilityOverrides#getVerticalPositionMultiplier(Configuration)}
* <p>
* Note that this is the final step that can change the resolved bounds. After this method
* is called, the position of the bounds will be moved to app space as sandboxing if the
@@ -8663,11 +8667,13 @@
} else {
navBarInsets = Insets.NONE;
}
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mAppCompatController.getAppCompatReachabilityOverrides();
// Horizontal position
int offsetX = 0;
if (parentBounds.width() != screenResolvedBoundsWidth) {
if (screenResolvedBoundsWidth <= parentAppBoundsWidth) {
- float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
+ float positionMultiplier = reachabilityOverrides.getHorizontalPositionMultiplier(
newParentConfiguration);
// If in immersive mode, always align to right and overlap right insets (task bar)
// as they are transient and hidden. This removes awkward right spacing.
@@ -8688,7 +8694,7 @@
int offsetY = 0;
if (parentBoundsHeight != screenResolvedBoundsHeight) {
if (screenResolvedBoundsHeight <= parentAppBoundsHeight) {
- float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
+ float positionMultiplier = reachabilityOverrides.getVerticalPositionMultiplier(
newParentConfiguration);
// If in immersive mode, always align to bottom and overlap bottom insets (nav bar,
// task bar) as they are transient and hidden. This removes awkward bottom spacing.
@@ -10686,6 +10692,9 @@
return true;
}
}
+ if (mAtmService.mBackNavigationController.isStartingSurfaceShown(this)) {
+ return true;
+ }
if (!super.isSyncFinished(group)) return false;
if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
.isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index aa63393..24ed1bb 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -23,7 +23,6 @@
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Environment;
-import android.os.SystemProperties;
import android.os.Trace;
import android.util.ArraySet;
import android.util.IntArray;
@@ -33,7 +32,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.window.flags.Flags;
import java.io.File;
import java.io.PrintWriter;
@@ -109,7 +107,6 @@
!service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots)
- && isSnapshotEnabled()
&& !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go
setSnapshotEnabled(snapshotEnabled);
}
@@ -121,12 +118,6 @@
return Math.max(Math.min(config, 1f), 0.1f);
}
- // TODO remove when enabled
- static boolean isSnapshotEnabled() {
- return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0
- || Flags.activitySnapshotByDefault();
- }
-
static PersistInfoProvider createPersistInfoProvider(
WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver) {
// Don't persist reduced file, instead we only persist the "HighRes" bitmap which has
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cc195ac..18aa9a0 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -28,6 +28,8 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
@@ -146,6 +148,7 @@
import java.lang.annotation.RetentionPolicy;
import java.text.DateFormat;
import java.util.Date;
+import java.util.function.Supplier;
/**
* Controller for interpreting how and then launching an activity.
@@ -1618,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
@@ -2079,9 +2084,23 @@
? targetTask.getWindowingMode() : displayContent.getWindowingMode();
final int launchingFromDisplayId =
mSourceRecord != null ? mSourceRecord.getDisplayId() : DEFAULT_DISPLAY;
+ final boolean isResultExpected = r.resultTo != null;
+ Supplier<IntentSender> intentSender = null;
+ if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ intentSender = () -> {
+ IIntentSender target = mService.getIntentSenderLocked(
+ ActivityManager.INTENT_SENDER_ACTIVITY, mRequest.callingPackage,
+ mRequest.callingFeatureId, mCallingUid, r.mUserId,
+ /* token= */ null, /* resultCode= */ null, /* requestCode= */ 0,
+ new Intent[]{ mIntent }, new String[]{ r.resolvedType },
+ FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT,
+ mOptions == null ? null : mOptions.toBundle());
+ return new IntentSender(target);
+ };
+ }
if (!displayContent.mDwpcHelper
.canActivityBeLaunched(r.info, r.intent, targetWindowingMode,
- launchingFromDisplayId, newTask)) {
+ launchingFromDisplayId, newTask, isResultExpected, intentSender)) {
Slog.w(TAG, "Abort to launch " + r.info.getComponentName()
+ " on display area " + mPreferredTaskDisplayArea);
return START_ABORTED;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1c14c5d..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;
}
@@ -7175,25 +7184,6 @@
}
/**
- * Checks if the given user is a visible background user, which is a full, background user
- * assigned to secondary displays on the devices that have
- * {@link UserManager#isVisibleBackgroundUsersEnabled()
- * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on
- * automotive builds, using the display associated with their seats).
- *
- * @see UserManager#isUserVisible()
- */
- private boolean isVisibleBackgroundUser(int userId) {
- if (!UserManager.isVisibleBackgroundUsersEnabled()) {
- return false;
- }
- boolean isForeground = getCurrentUserId() == userId;
- boolean isProfile = getUserManager().isProfile(userId);
- boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId);
- return isVisible && !isForeground && !isProfile;
- }
-
- /**
* In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to
* {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked}
* because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are
@@ -7208,7 +7198,7 @@
* @see ActivityTaskManagerService#updateShouldShowDialogsLocked
*/
private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) {
- if (!isVisibleBackgroundUser(userId)) {
+ if (!mWindowManager.mUmInternal.isVisibleBackgroundFullUser(userId)) {
return false;
}
final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId);
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/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 25cb134..d2f3d1d 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -50,8 +50,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.utils.OptPropFactory;
-import java.util.function.Function;
-
/**
* Encapsulates app compat configurations and overrides related to aspect ratio.
*/
@@ -76,20 +74,20 @@
@NonNull
private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
@NonNull
- private final Function<Boolean, Boolean> mIsDisplayFullScreenAndInPostureProvider;
+ private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
@NonNull
- private final Function<Configuration, Float> mGetHorizontalPositionMultiplierProvider;
+ private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
AppCompatAspectRatioOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder,
- @NonNull Function<Boolean, Boolean> isDisplayFullScreenAndInPostureProvider,
- @NonNull Function<Configuration, Float> getHorizontalPositionMultiplierProvider) {
+ @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery,
+ @NonNull AppCompatReachabilityOverrides appCompatReachabilityOverrides) {
mActivityRecord = activityRecord;
mAppCompatConfiguration = appCompatConfiguration;
+ mAppCompatDeviceStateQuery = appCompatDeviceStateQuery;
mUserAspectRatioState = new UserAspectRatioState();
- mIsDisplayFullScreenAndInPostureProvider = isDisplayFullScreenAndInPostureProvider;
- mGetHorizontalPositionMultiplierProvider = getHorizontalPositionMultiplierProvider;
+ mAppCompatReachabilityOverrides = appCompatReachabilityOverrides;
mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create(
@@ -245,12 +243,13 @@
}
private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) {
- final boolean isBookMode = mIsDisplayFullScreenAndInPostureProvider
- .apply(/* isTabletop */false);
- final boolean isNotCenteredHorizontally = mGetHorizontalPositionMultiplierProvider.apply(
- parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER;
- final boolean isTabletopMode = mIsDisplayFullScreenAndInPostureProvider
- .apply(/* isTabletop */ true);
+ final boolean isBookMode = mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */false);
+ final boolean isNotCenteredHorizontally =
+ mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(parentConfiguration)
+ != LETTERBOX_POSITION_MULTIPLIER_CENTER;
+ final boolean isTabletopMode = mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ true);
final boolean isLandscape = isFixedOrientationLandscape(
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 54223b6..d38edfc 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -35,7 +35,11 @@
@NonNull
private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
@NonNull
+ private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+ @NonNull
private final AppCompatOverrides mAppCompatOverrides;
+ @NonNull
+ private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -43,13 +47,16 @@
final PackageManager packageManager = wmService.mContext.getPackageManager();
final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
activityRecord.packageName);
+ mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord);
mTransparentPolicy = new TransparentPolicy(activityRecord,
wmService.mAppCompatConfiguration);
mAppCompatOverrides = new AppCompatOverrides(activityRecord,
- wmService.mAppCompatConfiguration, optPropBuilder);
+ wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery);
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
mTransparentPolicy, mAppCompatOverrides);
+ mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord,
+ wmService.mAppCompatConfiguration);
}
@NonNull
@@ -101,7 +108,23 @@
}
@NonNull
+ AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
+ return mAppCompatReachabilityPolicy;
+ }
+
+ @NonNull
AppCompatFocusOverrides getAppCompatFocusOverrides() {
return mAppCompatOverrides.getAppCompatFocusOverrides();
}
+
+ @NonNull
+ AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+ return mAppCompatOverrides.getAppCompatReachabilityOverrides();
+ }
+
+ @NonNull
+ AppCompatDeviceStateQuery getAppCompatDeviceStateQuery() {
+ return mAppCompatDeviceStateQuery;
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java b/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java
new file mode 100644
index 0000000..3abea24
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatDeviceStateQuery.java
@@ -0,0 +1,64 @@
+/*
+ * 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 android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.NonNull;
+
+/**
+ * Provides information about the current state of the display in relation of
+ * fold/unfold and other positions.
+ */
+class AppCompatDeviceStateQuery {
+
+ @NonNull
+ final ActivityRecord mActivityRecord;
+
+ AppCompatDeviceStateQuery(@NonNull ActivityRecord activityRecord) {
+ mActivityRecord = activityRecord;
+ }
+
+ /**
+ * Check if we are in the given pose and in fullscreen mode.
+ *
+ * Note that we check the task rather than the parent as with ActivityEmbedding the parent
+ * might be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ * actually fullscreen. If display is still in transition e.g. unfolding, don't return true
+ * for HALF_FOLDED state or app will flicker.
+ */
+ boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
+ final Task task = mActivityRecord.getTask();
+ final DisplayContent dc = mActivityRecord.mDisplayContent;
+ return dc != null && task != null && !dc.inTransition()
+ && dc.getDisplayRotation().isDeviceInPosture(
+ DeviceStateController.DeviceState.HALF_FOLDED, isTabletop)
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ /**
+ * Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+ * be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ * actually fullscreen.
+ */
+ boolean isDisplayFullScreenAndSeparatingHinge() {
+ final Task task = mActivityRecord.getTask();
+ return mActivityRecord.mDisplayContent != null && task != null
+ && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 4450011..80bbee3 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -35,19 +35,22 @@
private final AppCompatFocusOverrides mAppCompatFocusOverrides;
@NonNull
private final AppCompatResizeOverrides mAppCompatResizeOverrides;
+ @NonNull
+ private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
- @NonNull OptPropFactory optPropBuilder) {
+ @NonNull OptPropFactory optPropBuilder,
+ @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
- // TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability.
+ mAppCompatReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
+ appCompatConfiguration, appCompatDeviceStateQuery);
mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder,
- activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture,
- activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier);
+ appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
+ mAppCompatReachabilityOverrides);
mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder);
@@ -77,4 +80,9 @@
AppCompatResizeOverrides getAppCompatResizeOverrides() {
return mAppCompatResizeOverrides;
}
+
+ @NonNull
+ AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
+ return mAppCompatReachabilityOverrides;
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
new file mode 100644
index 0000000..caff96b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityOverrides.java
@@ -0,0 +1,340 @@
+/*
+ * 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 android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.window.flags.Flags;
+
+/**
+ * Encapsulate overrides and configurations about app compat reachability.
+ */
+class AppCompatReachabilityOverrides {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatConfiguration mAppCompatConfiguration;
+ @NonNull
+ private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
+ @NonNull
+ private final ReachabilityState mReachabilityState;
+
+ AppCompatReachabilityOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatConfiguration appCompatConfiguration,
+ @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
+ mActivityRecord = activityRecord;
+ mAppCompatConfiguration = appCompatConfiguration;
+ mAppCompatDeviceStateQuery = appCompatDeviceStateQuery;
+ mReachabilityState = new ReachabilityState();
+ }
+
+ boolean isFromDoubleTap() {
+ return mReachabilityState.isFromDoubleTap();
+ }
+
+ boolean isDoubleTapEvent() {
+ return mReachabilityState.mIsDoubleTapEvent;
+ }
+
+ void setDoubleTapEvent() {
+ mReachabilityState.mIsDoubleTapEvent = true;
+ }
+
+ /**
+ * Provides the multiplier to use when calculating the position of a letterboxed app after
+ * an horizontal reachability event (double tap). The method takes the current state of the
+ * device (e.g. device in book mode) into account.
+ * </p>
+ * @param parentConfiguration The parent {@link Configuration}.
+ * @return The value to use for calculating the letterbox horizontal position.
+ */
+ float getHorizontalPositionMultiplier(@NonNull Configuration parentConfiguration) {
+ // Don't check resolved configuration because it may not be updated yet during
+ // configuration change.
+ boolean bookModeEnabled = isFullScreenAndBookModeEnabled();
+ return isHorizontalReachabilityEnabled(parentConfiguration)
+ // Using the last global dynamic position to avoid "jumps" when moving
+ // between apps or activities.
+ ? mAppCompatConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled)
+ : mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled);
+ }
+
+ /**
+ * Provides the multiplier to use when calculating the position of a letterboxed app after
+ * a vertical reachability event (double tap). The method takes the current state of the
+ * device (e.g. device posture) into account.
+ * </p>
+ * @param parentConfiguration The parent {@link Configuration}.
+ * @return The value to use for calculating the letterbox horizontal position.
+ */
+ float getVerticalPositionMultiplier(@NonNull Configuration parentConfiguration) {
+ // Don't check resolved configuration because it may not be updated yet during
+ // configuration change.
+ boolean tabletopMode = mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ true);
+ return isVerticalReachabilityEnabled(parentConfiguration)
+ // Using the last global dynamic position to avoid "jumps" when moving
+ // between apps or activities.
+ ? mAppCompatConfiguration.getVerticalMultiplierForReachability(tabletopMode)
+ : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
+ }
+
+ boolean isHorizontalReachabilityEnabled() {
+ return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ }
+
+ boolean isVerticalReachabilityEnabled() {
+ return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ }
+
+ boolean isLetterboxDoubleTapEducationEnabled() {
+ return isHorizontalReachabilityEnabled() || isVerticalReachabilityEnabled();
+ }
+
+ @AppCompatConfiguration.LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ final boolean isInFullScreenTabletopMode =
+ mAppCompatDeviceStateQuery.isDisplayFullScreenAndSeparatingHinge();
+ return mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(
+ isInFullScreenTabletopMode);
+ }
+
+ @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled();
+ return mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(
+ isInFullScreenBookMode);
+ }
+
+ int getLetterboxPositionForLogging() {
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+ if (isHorizontalReachabilityEnabled()) {
+ int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForHorizontalReachability(mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ false));
+ positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPositionForLogging(
+ letterboxPositionForHorizontalReachability);
+ } else if (isVerticalReachabilityEnabled()) {
+ int letterboxPositionForVerticalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForVerticalReachability(mAppCompatDeviceStateQuery
+ .isDisplayFullScreenAndInPosture(/* isTabletop */ true));
+ positionToLog = letterboxVerticalReachabilityPositionToLetterboxPositionForLogging(
+ letterboxPositionForVerticalReachability);
+ }
+ return positionToLog;
+ }
+
+ /**
+ * @return {@value true} if the vertical reachability should be allowed in case of
+ * thin letterboxing.
+ */
+ boolean allowVerticalReachabilityForThinLetterbox() {
+ if (!Flags.disableThinLetterboxingPolicy()) {
+ return true;
+ }
+ // When the flag is enabled we allow vertical reachability only if the
+ // app is not thin letterboxed vertically.
+ return !isVerticalThinLetterboxed();
+ }
+
+ /**
+ * @return {@value true} if the horizontal reachability should be enabled in case of
+ * thin letterboxing.
+ */
+ boolean allowHorizontalReachabilityForThinLetterbox() {
+ if (!Flags.disableThinLetterboxingPolicy()) {
+ return true;
+ }
+ // When the flag is enabled we allow horizontal reachability only if the
+ // app is not thin pillarboxed.
+ return !isHorizontalThinLetterboxed();
+ }
+
+ /**
+ * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
+ */
+ boolean isVerticalThinLetterboxed() {
+ final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx();
+ if (thinHeight < 0) {
+ return false;
+ }
+ final Task task = mActivityRecord.getTask();
+ if (task == null) {
+ return false;
+ }
+ final int padding = Math.abs(
+ task.getBounds().height() - mActivityRecord.getBounds().height()) / 2;
+ return padding <= thinHeight;
+ }
+
+ /**
+ * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
+ */
+ boolean isHorizontalThinLetterboxed() {
+ final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx();
+ if (thinWidth < 0) {
+ return false;
+ }
+ final Task task = mActivityRecord.getTask();
+ if (task == null) {
+ return false;
+ }
+ final int padding = Math.abs(
+ task.getBounds().width() - mActivityRecord.getBounds().width()) / 2;
+ return padding <= thinWidth;
+ }
+
+ // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+ // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+ // actually fullscreen.
+ private boolean isDisplayFullScreenAndSeparatingHinge() {
+ Task task = mActivityRecord.getTask();
+ return mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
+ && task != null
+ && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
+ private int letterboxHorizontalReachabilityPositionToLetterboxPositionForLogging(
+ @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox horizontal reachability position type: "
+ + position);
+ }
+ }
+
+ private int letterboxVerticalReachabilityPositionToLetterboxPositionForLogging(
+ @AppCompatConfiguration.LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox vertical reachability position type: "
+ + position);
+ }
+ }
+
+ private boolean isFullScreenAndBookModeEnabled() {
+ return mAppCompatDeviceStateQuery.isDisplayFullScreenAndInPosture(/* isTabletop */ false)
+ && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
+ }
+
+ /**
+ * Whether horizontal reachability is enabled for an activity in the current configuration.
+ *
+ * <p>Conditions that needs to be met:
+ * <ul>
+ * <li>Windowing mode is fullscreen.
+ * <li>Horizontal Reachability is enabled.
+ * <li>First top opaque activity fills parent vertically, but not horizontally.
+ * </ul>
+ */
+ private boolean isHorizontalReachabilityEnabled(@NonNull Configuration parentConfiguration) {
+ if (!allowHorizontalReachabilityForThinLetterbox()) {
+ return false;
+ }
+ final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
+ final Rect parentAppBounds = parentAppBoundsOverride != null
+ ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
+ // Use screen resolved bounds which uses resolved bounds or size compat bounds
+ // as activity bounds can sometimes be empty
+ final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
+ .getTransparentPolicy().getFirstOpaqueActivity()
+ .map(ActivityRecord::getScreenResolvedBounds)
+ .orElse(mActivityRecord.getScreenResolvedBounds());
+ return mAppCompatConfiguration.getIsHorizontalReachabilityEnabled()
+ && parentConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN
+ // Check whether the activity fills the parent vertically.
+ && parentAppBounds.height() <= opaqueActivityBounds.height()
+ && parentAppBounds.width() > opaqueActivityBounds.width();
+ }
+
+ /**
+ * Whether vertical reachability is enabled for an activity in the current configuration.
+ *
+ * <p>Conditions that needs to be met:
+ * <ul>
+ * <li>Windowing mode is fullscreen.
+ * <li>Vertical Reachability is enabled.
+ * <li>First top opaque activity fills parent horizontally but not vertically.
+ * </ul>
+ */
+ private boolean isVerticalReachabilityEnabled(@NonNull Configuration parentConfiguration) {
+ if (!allowVerticalReachabilityForThinLetterbox()) {
+ return false;
+ }
+ final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
+ final Rect parentAppBounds = parentAppBoundsOverride != null
+ ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
+ // Use screen resolved bounds which uses resolved bounds or size compat bounds
+ // as activity bounds can sometimes be empty.
+ final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
+ .getTransparentPolicy().getFirstOpaqueActivity()
+ .map(ActivityRecord::getScreenResolvedBounds)
+ .orElse(mActivityRecord.getScreenResolvedBounds());
+ return mAppCompatConfiguration.getIsVerticalReachabilityEnabled()
+ && parentConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN
+ // Check whether the activity fills the parent horizontally.
+ && parentAppBounds.width() <= opaqueActivityBounds.width()
+ && parentAppBounds.height() > opaqueActivityBounds.height();
+ }
+
+ private static class ReachabilityState {
+ // If the current event is a double tap.
+ private boolean mIsDoubleTapEvent;
+
+ boolean isFromDoubleTap() {
+ final boolean isFromDoubleTap = mIsDoubleTapEvent;
+ mIsDoubleTapEvent = false;
+ return isFromDoubleTap;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
new file mode 100644
index 0000000..c3bf116
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -0,0 +1,181 @@
+/*
+ * 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.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.function.Supplier;
+
+/**
+ * Encapsulate logic about app compat reachability.
+ */
+class AppCompatReachabilityPolicy {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+ @NonNull
+ private final AppCompatConfiguration mAppCompatConfiguration;
+ @Nullable
+ @VisibleForTesting
+ Supplier<Rect> mLetterboxInnerBoundsSupplier;
+
+ AppCompatReachabilityPolicy(@NonNull ActivityRecord activityRecord,
+ @NonNull AppCompatConfiguration appCompatConfiguration) {
+ mActivityRecord = activityRecord;
+ mAppCompatConfiguration = appCompatConfiguration;
+ }
+
+ /**
+ * To handle reachability a supplier for the current letterox inner bounds is required.
+ * <p/>
+ * @param letterboxInnerBoundsSupplier The supplier for the letterbox inner bounds.
+ */
+ void setLetterboxInnerBoundsSupplier(@Nullable Supplier<Rect> letterboxInnerBoundsSupplier) {
+ mLetterboxInnerBoundsSupplier = letterboxInnerBoundsSupplier;
+ }
+
+ /**
+ * Handles double tap events for reachability.
+ * <p/>
+ * @param x Double tap x coordinate.
+ * @param y Double tap y coordinate.
+ */
+ void handleDoubleTap(int x, int y) {
+ handleHorizontalDoubleTap(x);
+ handleVerticalDoubleTap(y);
+ }
+
+ private void handleHorizontalDoubleTap(int x) {
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ if (!reachabilityOverrides.isHorizontalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
+ return;
+ }
+ final Rect letterboxInnerFrame = getLetterboxInnerFrame();
+ if (letterboxInnerFrame.left <= x && letterboxInnerFrame.right >= x) {
+ // Only react to clicks at the sides of the letterboxed app window.
+ return;
+ }
+ final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController
+ .getAppCompatDeviceStateQuery();
+ final boolean isInFullScreenBookMode = deviceStateQuery
+ .isDisplayFullScreenAndSeparatingHinge()
+ && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
+ final int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
+ if (letterboxInnerFrame.left > x) {
+ // Moving to the next stop on the left side of the app window: right > center > left.
+ mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextLeftStop(
+ isInFullScreenBookMode);
+ int letterboxPositionChangeForLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ } else if (letterboxInnerFrame.right < x) {
+ // Moving to the next stop on the right side of the app window: left > center > right.
+ mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
+ isInFullScreenBookMode);
+ final int letterboxPositionChangeForLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ }
+ // TODO(197549949): Add animation for transition.
+ mActivityRecord.recomputeConfiguration();
+ }
+
+ private void handleVerticalDoubleTap(int y) {
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+ if (!reachabilityOverrides.isVerticalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
+ return;
+ }
+ final Rect letterboxInnerFrame = getLetterboxInnerFrame();
+ if (letterboxInnerFrame.top <= y && letterboxInnerFrame.bottom >= y) {
+ // Only react to clicks at the top and bottom of the letterboxed app window.
+ return;
+ }
+ final AppCompatDeviceStateQuery deviceStateQuery = mActivityRecord.mAppCompatController
+ .getAppCompatDeviceStateQuery();
+ final boolean isInFullScreenTabletopMode = deviceStateQuery
+ .isDisplayFullScreenAndSeparatingHinge();
+ final int letterboxPositionForVerticalReachability = mAppCompatConfiguration
+ .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode);
+ if (letterboxInnerFrame.top > y) {
+ // Moving to the next stop on the top side of the app window: bottom > center > top.
+ mAppCompatConfiguration.movePositionForVerticalReachabilityToNextTopStop(
+ isInFullScreenTabletopMode);
+ final int letterboxPositionChangeForLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ } else if (letterboxInnerFrame.bottom < y) {
+ // Moving to the next stop on the bottom side of the app window: top > center > bottom.
+ mAppCompatConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
+ isInFullScreenTabletopMode);
+ final int letterboxPositionChangeForLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+ logLetterboxPositionChange(letterboxPositionChangeForLog);
+ reachabilityOverrides.setDoubleTapEvent();
+ }
+ // TODO(197549949): Add animation for transition.
+ mActivityRecord.recomputeConfiguration();
+ }
+
+ /**
+ * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+ */
+ private void logLetterboxPositionChange(int letterboxPositionChangeForLog) {
+ mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+ .logLetterboxPositionChange(mActivityRecord, letterboxPositionChangeForLog);
+ }
+
+ @NonNull
+ private Rect getLetterboxInnerFrame() {
+ return mLetterboxInnerBoundsSupplier != null ? mLetterboxInnerBoundsSupplier.get()
+ : new Rect();
+ }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index a4cb389..0244d27 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -63,7 +63,7 @@
/**
* Returns the aspect ratio of the given {@code rect}.
*/
- static float computeAspectRatio(Rect rect) {
+ static float computeAspectRatio(@NonNull Rect rect) {
final int width = rect.width();
final int height = rect.height();
if (width == 0 || height == 0) {
@@ -89,75 +89,138 @@
return activityRecord.info.isChangeEnabled(overrideChangeId);
}
+ /**
+ * Attempts to return the app bounds (bounds without insets) of the top most opaque activity. If
+ * these are not available, it defaults to the bounds of the activity which include insets. In
+ * the event the activity is in Size Compat Mode, the Size Compat bounds are returned instead.
+ */
+ @NonNull
+ static Rect getAppBounds(@NonNull ActivityRecord activityRecord) {
+ // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
+ final Rect appBounds = activityRecord.getConfiguration().windowConfiguration.getAppBounds();
+ if (appBounds == null) {
+ return activityRecord.getBounds();
+ }
+ return activityRecord.mAppCompatController.getTransparentPolicy()
+ .findOpaqueNotFinishingActivityBelow()
+ .map(AppCompatUtils::getAppBounds)
+ .orElseGet(() -> {
+ if (activityRecord.hasSizeCompatBounds()) {
+ return activityRecord.getScreenResolvedBounds();
+ }
+ return appBounds;
+ });
+ }
+
static void fillAppCompatTaskInfo(@NonNull Task task, @NonNull TaskInfo info,
@Nullable ActivityRecord top) {
final AppCompatTaskInfo appCompatTaskInfo = info.appCompatTaskInfo;
- appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
- CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
+ clearAppCompatTaskInfo(appCompatTaskInfo);
if (top == null) {
return;
}
-
+ final AppCompatReachabilityOverrides reachabilityOverrides = top.mAppCompatController
+ .getAppCompatReachabilityOverrides();
final boolean isTopActivityResumed = top.getOrganizedTask() == task && top.isState(RESUMED);
final boolean isTopActivityVisible = top.getOrganizedTask() == task && top.isVisible();
// Whether the direct top activity is in size compat mode.
- appCompatTaskInfo.topActivityInSizeCompat = isTopActivityVisible && top.inSizeCompatMode();
- if (appCompatTaskInfo.topActivityInSizeCompat
+ appCompatTaskInfo.setTopActivityInSizeCompat(
+ isTopActivityVisible && top.inSizeCompatMode());
+ if (appCompatTaskInfo.isTopActivityInSizeCompat()
&& top.mWmService.mAppCompatConfiguration.isTranslucentLetterboxingEnabled()) {
// We hide the restart button in case of transparent activities.
- appCompatTaskInfo.topActivityInSizeCompat = top.fillsParent();
+ appCompatTaskInfo.setTopActivityInSizeCompat(top.fillsParent());
}
// Whether the direct top activity is eligible for letterbox education.
- appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed
- && top.isEligibleForLetterboxEducation();
- appCompatTaskInfo.isLetterboxEducationEnabled = top.mLetterboxUiController
- .isLetterboxEducationEnabled();
+ appCompatTaskInfo.setEligibleForLetterboxEducation(
+ isTopActivityResumed && top.isEligibleForLetterboxEducation());
+ appCompatTaskInfo.setLetterboxEducationEnabled(top.mLetterboxUiController
+ .isLetterboxEducationEnabled());
- appCompatTaskInfo.isUserFullscreenOverrideEnabled = top.mAppCompatController
- .getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
- appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top.mAppCompatController
- .getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ top.mAppCompatController.getAppCompatAspectRatioOverrides();
+ appCompatTaskInfo.setUserFullscreenOverrideEnabled(
+ aspectRatioOverrides.shouldApplyUserFullscreenOverride());
+ appCompatTaskInfo.setSystemFullscreenOverrideEnabled(
+ aspectRatioOverrides.isSystemOverrideToFullscreenEnabled());
- appCompatTaskInfo.isFromLetterboxDoubleTap = top.mLetterboxUiController.isFromDoubleTap();
- appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
- appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+ appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
+
+ final Rect bounds = top.getBounds();
+ final Rect appBounds = getAppBounds(top);
+ appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
+ appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
+ appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
+ appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
// We need to consider if letterboxed or pillarboxed.
// TODO(b/336807329) Encapsulate reachability logic
- appCompatTaskInfo.isLetterboxDoubleTapEnabled = top.mLetterboxUiController
- .isLetterboxDoubleTapEducationEnabled();
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
+ .isLetterboxDoubleTapEducationEnabled());
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
if (appCompatTaskInfo.isTopActivityPillarboxed()) {
- if (top.mLetterboxUiController.allowHorizontalReachabilityForThinLetterbox()) {
+ if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
// Pillarboxed.
appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
- top.mLetterboxUiController
- .getLetterboxPositionForHorizontalReachability();
+ reachabilityOverrides.getLetterboxPositionForHorizontalReachability();
} else {
- appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
}
} else {
- if (top.mLetterboxUiController.allowVerticalReachabilityForThinLetterbox()) {
+ if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
// Letterboxed.
appCompatTaskInfo.topActivityLetterboxVerticalPosition =
- top.mLetterboxUiController
- .getLetterboxPositionForVerticalReachability();
+ reachabilityOverrides.getLetterboxPositionForVerticalReachability();
} else {
- appCompatTaskInfo.isLetterboxDoubleTapEnabled = false;
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
}
}
}
- appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
- !info.isTopActivityTransparent && !appCompatTaskInfo.topActivityInSizeCompat
- && top.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldEnableUserAspectRatioSettings();
- appCompatTaskInfo.topActivityBoundsLetterboxed = top.areBoundsLetterboxed();
+ final boolean eligibleForAspectRatioButton =
+ !info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat()
+ && aspectRatioOverrides.shouldEnableUserAspectRatioSettings();
+ appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton);
+ appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController
.getAppCompatCameraOverrides().getFreeformCameraCompatMode();
}
+
+ /**
+ * Returns a string representing the reason for letterboxing. This method assumes the activity
+ * is letterboxed.
+ * @param activityRecord The {@link ActivityRecord} for the letterboxed activity.
+ * @param mainWin The {@link WindowState} used to letterboxing.
+ */
+ @NonNull
+ static String getLetterboxReasonString(@NonNull ActivityRecord activityRecord,
+ @NonNull WindowState mainWin) {
+ if (activityRecord.inSizeCompatMode()) {
+ return "SIZE_COMPAT_MODE";
+ }
+ final AppCompatAspectRatioPolicy aspectRatioPolicy = activityRecord.mAppCompatController
+ .getAppCompatAspectRatioPolicy();
+ if (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()) {
+ return "FIXED_ORIENTATION";
+ }
+ if (mainWin.isLetterboxedForDisplayCutout()) {
+ return "DISPLAY_CUTOUT";
+ }
+ if (aspectRatioPolicy.isLetterboxedForAspectRatioOnly()) {
+ return "ASPECT_RATIO";
+ }
+ return "UNKNOWN_REASON";
+ }
+
+ private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) {
+ info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxAppHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
+ info.cameraCompatTaskInfo.freeformCameraCompatMode =
+ CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
+ info.clearTopActivityFlags();
+ }
}
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/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 78636a7..5a0cbf3 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -250,7 +250,7 @@
ArraySet<ActivityRecord> tmpOpenApps = mDisplayContent.mOpeningApps;
ArraySet<ActivityRecord> tmpCloseApps = mDisplayContent.mClosingApps;
- if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringTransition()) {
+ if (mDisplayContent.mAtmService.mBackNavigationController.isMonitoringFinishTransition()) {
tmpOpenApps = new ArraySet<>(mDisplayContent.mOpeningApps);
tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps);
if (mDisplayContent.mAtmService.mBackNavigationController
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 0f8d68b..06e665e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
@@ -47,6 +48,7 @@
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.RemoteAnimationTarget;
@@ -83,11 +85,6 @@
private AnimationHandler mAnimationHandler;
- /**
- * The transition who match the back navigation targets,
- * release animation after this transition finish.
- */
- private Transition mWaitTransitionFinish;
private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
@@ -143,7 +140,7 @@
BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
synchronized (wmService.mGlobalLock) {
- if (isMonitoringTransition()) {
+ if (isMonitoringFinishTransition()) {
Slog.w(TAG, "Previous animation hasn't finish, status: " + mAnimationHandler);
// Don't start any animation for it.
return null;
@@ -308,6 +305,7 @@
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevTask.isActivityTypeHome()) {
removedWindowContainer = currentTask;
+ prevTask = prevTask.getRootTask();
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
final ActivityRecord ar = prevTask.getTopNonFinishingActivity();
mShowWallpaper = ar != null && ar.hasWallpaper();
@@ -562,10 +560,15 @@
return !prevActivities.isEmpty();
}
- boolean isMonitoringTransition() {
+ boolean isMonitoringFinishTransition() {
return mAnimationHandler.mComposed || mNavigationMonitor.isMonitorForRemote();
}
+ boolean isMonitoringPrepareTransition(Transition transition) {
+ return mAnimationHandler.mComposed
+ && mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == transition;
+ }
+
private void scheduleAnimation(@NonNull AnimationHandler.ScheduleAnimationBuilder builder) {
mPendingAnimation = builder.build();
mWindowManagerService.mWindowPlacerLocked.requestTraversal();
@@ -576,7 +579,9 @@
}
private boolean isWaitBackTransition() {
- return mAnimationHandler.mComposed && mAnimationHandler.mWaitTransition;
+ // Ignore mWaitTransition while flag is enabled.
+ return mAnimationHandler.mComposed && (Flags.migratePredictiveBackTransition()
+ || mAnimationHandler.mWaitTransition);
}
boolean isKeyguardOccluded(WindowState focusWindow) {
@@ -626,7 +631,7 @@
*/
boolean removeIfContainsBackAnimationTargets(ArraySet<ActivityRecord> openApps,
ArraySet<ActivityRecord> closeApps) {
- if (!isMonitoringTransition()) {
+ if (!isMonitoringFinishTransition()) {
return false;
}
mTmpCloseApps.addAll(closeApps);
@@ -652,7 +657,6 @@
final ActivityRecord ar = openApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, true /* open */)) {
openApps.removeAt(i);
- mAnimationHandler.markStartingSurfaceMatch(null /* reparentTransaction */);
}
}
for (int i = closeApps.size() - 1; i >= 0; --i) {
@@ -670,6 +674,12 @@
mAnimationHandler.markWindowHasDrawn(openActivity);
}
+ boolean isStartingSurfaceShown(ActivityRecord openActivity) {
+ if (!Flags.migratePredictiveBackTransition()) {
+ return false;
+ }
+ return mAnimationHandler.isStartingSurfaceDrawn(openActivity);
+ }
@VisibleForTesting
class NavigationMonitor {
// The window which triggering the back navigation.
@@ -758,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.
@@ -767,8 +819,20 @@
* open or close list.
*/
void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets,
- SurfaceControl.Transaction startTransaction) {
- if (!isMonitoringTransition() || targets.isEmpty()) {
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ if (isMonitoringPrepareTransition(transition)) {
+ // Flag target matches and prepare to remove windowless surface.
+ mAnimationHandler.markStartingSurfaceMatch(startTransaction);
+ return;
+ }
+ if (targets.isEmpty()) {
+ return;
+ }
+ final boolean migratePredictToTransition = Flags.migratePredictiveBackTransition();
+ if (migratePredictToTransition && !mAnimationHandler.mComposed) {
+ return;
+ } else if (!isMonitoringFinishTransition()) {
return;
}
if (mAnimationHandler.hasTargetDetached()) {
@@ -792,51 +856,68 @@
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 (mWaitTransitionFinish != null) {
+ if (mAnimationHandler.mPrepareCloseTransition != null) {
Slog.e(TAG, "Gesture animation is applied on another transition?");
}
- mWaitTransitionFinish = transition;
- // Flag target matches to defer remove the splash screen.
- for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mTmpOpenApps.get(i);
- if (mAnimationHandler.isTarget(wc, true /* open */)) {
- mAnimationHandler.markStartingSurfaceMatch(startTransaction);
- break;
- }
+ mAnimationHandler.mPrepareCloseTransition = transition;
+ 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());
}
+ // Flag target matches and prepare to remove windowless surface.
+ mAnimationHandler.markStartingSurfaceMatch(startTransaction);
// release animation leash
if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) {
- startTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
+ finishTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null;
}
- // 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());
}
mTmpOpenApps.clear();
mTmpCloseApps.clear();
}
boolean isMonitorTransitionTarget(WindowContainer wc) {
- if (!isWaitBackTransition() || mWaitTransitionFinish == null) {
- return false;
+ 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 */);
}
- return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
+ return false;
}
boolean shouldPauseTouch(WindowContainer wc) {
// Once the close transition is ready, it means the onBackInvoked callback has invoked, and
// app is ready to trigger next transition, no matter what it will be.
- return mAnimationHandler.mComposed && mWaitTransitionFinish == null
+ return mAnimationHandler.mComposed && mAnimationHandler.mPrepareCloseTransition == null
&& mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
@@ -847,7 +928,6 @@
void clearBackAnimations(boolean cancel) {
mAnimationHandler.clearBackAnimateTarget(cancel);
mNavigationMonitor.stopMonitorTransition();
- mWaitTransitionFinish = null;
}
/**
@@ -858,7 +938,13 @@
*/
void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
@NonNull Transition finishedTransition) {
- if (finishedTransition == mWaitTransitionFinish) {
+ if (isMonitoringPrepareTransition(finishedTransition)) {
+ if (mAnimationHandler.mPrepareCloseTransition == null) {
+ clearBackAnimations(true /* cancel */);
+ }
+ return;
+ }
+ if (finishedTransition == mAnimationHandler.mPrepareCloseTransition) {
clearBackAnimations(false /* cancel */);
}
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
@@ -938,13 +1024,13 @@
// the opening target like starting window do.
private boolean mStartingSurfaceTargetMatch;
private ActivityRecord[] mOpenActivities;
+ Transition mPrepareCloseTransition;
AnimationHandler(WindowManagerService wms) {
mWindowManagerService = wms;
final Context context = wms.mContext;
mShowWindowlessSurface = context.getResources().getBoolean(
- com.android.internal.R.bool.config_predictShowStartingSurface)
- && Flags.activitySnapshotByDefault();
+ com.android.internal.R.bool.config_predictShowStartingSurface);
}
private static final int UNKNOWN = 0;
private static final int TASK_SWITCH = 1;
@@ -982,6 +1068,12 @@
@NonNull ActivityRecord[] openingActivities) {
if (isActivitySwitch(close, open)) {
mSwitchType = ACTIVITY_SWITCH;
+ if (Flags.migratePredictiveBackTransition()) {
+ final Pair<WindowContainer, WindowContainer[]> replaced =
+ promoteToTFIfNeeded(close, open);
+ close = replaced.first;
+ open = replaced.second;
+ }
} else if (isTaskSwitch(close, open)) {
mSwitchType = TASK_SWITCH;
} else if (isDialogClose(close)) {
@@ -1019,6 +1111,34 @@
mOpenActivities = openingActivities;
}
+ private Pair<WindowContainer, WindowContainer[]> promoteToTFIfNeeded(
+ WindowContainer close, WindowContainer[] open) {
+ WindowContainer replaceClose = close;
+ TaskFragment closeTF = close.asActivityRecord().getTaskFragment();
+ if (closeTF != null && !closeTF.isEmbedded()) {
+ closeTF = null;
+ }
+ final WindowContainer[] replaceOpen = new WindowContainer[open.length];
+ if (open.length >= 2) { // Promote to TaskFragment
+ for (int i = open.length - 1; i >= 0; --i) {
+ replaceOpen[i] = open[i].asActivityRecord().getTaskFragment();
+ replaceClose = closeTF != null ? closeTF : close;
+ }
+ } else {
+ TaskFragment openTF = open[0].asActivityRecord().getTaskFragment();
+ if (openTF != null && !openTF.isEmbedded()) {
+ openTF = null;
+ }
+ if (closeTF != openTF) {
+ replaceOpen[0] = openTF != null ? openTF : open[0];
+ replaceClose = closeTF != null ? closeTF : close;
+ } else {
+ replaceOpen[0] = open[0];
+ }
+ }
+ return new Pair<>(replaceClose, replaceOpen);
+ }
+
private boolean composeAnimations(@NonNull WindowContainer close,
@NonNull WindowContainer[] open, @NonNull ActivityRecord[] openingActivities) {
if (mComposed || mWaitTransition) {
@@ -1080,7 +1200,8 @@
}
void markWindowHasDrawn(ActivityRecord activity) {
- if (!mComposed || mWaitTransition) {
+ if (!mComposed || mWaitTransition || mOpenAnimAdaptor.mPreparedOpenTransition == null
+ || mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
return;
}
boolean allWindowDrawn = true;
@@ -1096,6 +1217,17 @@
}
}
+ boolean isStartingSurfaceDrawn(ActivityRecord activity) {
+ // Check whether we create windowless surface to prepare open transition
+ if (!mComposed || mOpenAnimAdaptor.mPreparedOpenTransition == null) {
+ return false;
+ }
+ if (isTarget(activity, true /* open */)) {
+ return mOpenAnimAdaptor.mStartingSurface != null;
+ }
+ return false;
+ }
+
private static boolean isAnimateTarget(@NonNull WindowContainer window,
@NonNull WindowContainer animationTarget, int switchType) {
if (switchType == TASK_SWITCH) {
@@ -1109,7 +1241,9 @@
&& window.hasChild(animationTarget));
} else if (switchType == ACTIVITY_SWITCH) {
return window == animationTarget
- || (window.asTaskFragment() != null && window.hasChild(animationTarget));
+ || (window.asTaskFragment() != null && window.hasChild(animationTarget))
+ || (animationTarget.asTaskFragment() != null
+ && animationTarget.hasChild(window));
}
return false;
}
@@ -1122,8 +1256,14 @@
resetActivity.mDisplayContent
.continueUpdateOrientationForDiffOrienLaunchingApp();
}
+ final Transition finishTransition =
+ resetActivity.mTransitionController.mFinishingTransition;
+ final boolean inFinishTransition = finishTransition != null
+ && (mPrepareCloseTransition == finishTransition
+ || (mOpenAnimAdaptor != null
+ && mOpenAnimAdaptor.mPreparedOpenTransition == finishTransition));
if (resetActivity.mLaunchTaskBehind) {
- restoreLaunchBehind(resetActivity, cancel);
+ restoreLaunchBehind(resetActivity, cancel, inFinishTransition);
}
}
}
@@ -1137,12 +1277,25 @@
}
}
- void markStartingSurfaceMatch(SurfaceControl.Transaction reparentTransaction) {
+ void markStartingSurfaceMatch(SurfaceControl.Transaction startTransaction) {
if (mStartingSurfaceTargetMatch) {
return;
}
mStartingSurfaceTargetMatch = true;
- mOpenAnimAdaptor.reparentWindowlessSurfaceToTarget(reparentTransaction);
+
+ if (mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ final SurfaceControl startingSurface = mOpenAnimAdaptor.mStartingSurface;
+ if (startingSurface != null && startingSurface.isValid()) {
+ startTransaction.addTransactionCommittedListener(Runnable::run, () -> {
+ synchronized (mWindowManagerService.mGlobalLock) {
+ if (mOpenAnimAdaptor != null) {
+ mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
+ }
+ }
+ });
+ }
}
void clearBackAnimateTarget(boolean cancel) {
@@ -1150,6 +1303,7 @@
mComposed = false;
finishPresentAnimations(cancel);
}
+ mPrepareCloseTransition = null;
mWaitTransition = false;
mStartingSurfaceTargetMatch = false;
mSwitchType = UNKNOWN;
@@ -1239,6 +1393,9 @@
// requested one during animating.
private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
private SurfaceControl mStartingSurface;
+
+ private Transition mPreparedOpenTransition;
+
BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
@NonNull WindowContainer... targets) {
mAdaptors = new BackWindowAnimationAdaptor[targets.length];
@@ -1267,6 +1424,8 @@
mCloseTransaction.apply();
mCloseTransaction = null;
}
+
+ mPreparedOpenTransition = null;
}
private RemoteAnimationTarget createWrapTarget() {
@@ -1279,8 +1438,7 @@
unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
}
final WindowContainer wc = mAdaptors[0].mTarget;
- final Task task = wc.asActivityRecord() != null
- ? wc.asActivityRecord().getTask() : wc.asTask();
+ final Task task = mAdaptors[0].getTopTask();
final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
final SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName("cross-animation-leash")
@@ -1293,7 +1451,7 @@
mCloseTransaction = new SurfaceControl.Transaction();
mCloseTransaction.reparent(leashSurface, null);
final SurfaceControl.Transaction pt = wc.getPendingTransaction();
- pt.setLayer(leashSurface, wc.getParent().getLastLayer());
+ pt.setLayer(leashSurface, wc.getLastLayer());
for (int i = mAdaptors.length - 1; i >= 0; --i) {
BackWindowAnimationAdaptor adaptor = mAdaptors[i];
pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
@@ -1323,15 +1481,20 @@
}
final WindowContainer mainOpen = mAdaptors[0].mTarget;
final int switchType = mAdaptors[0].mSwitchType;
- final Task openTask = switchType == TASK_SWITCH
- ? mainOpen.asTask() : switchType == ACTIVITY_SWITCH
- ? mainOpen.asActivityRecord().getTask() : null;
+ final Task openTask = mAdaptors[0].getTopTask();
if (openTask == null) {
return;
}
- final ActivityRecord mainActivity = switchType == ACTIVITY_SWITCH
- ? mainOpen.asActivityRecord()
- : openTask.getTopNonFinishingActivity();
+ ActivityRecord mainActivity = null;
+ if (switchType == ACTIVITY_SWITCH) {
+ mainActivity = mainOpen.asActivityRecord();
+ if (mainActivity == null && mainOpen.asTaskFragment() != null) {
+ mainActivity = mainOpen.asTaskFragment().getTopNonFinishingActivity();
+ }
+ }
+ if (mainActivity == null) {
+ mainActivity = openTask.getTopNonFinishingActivity();
+ }
if (mainActivity == null) {
return;
}
@@ -1363,6 +1526,8 @@
synchronized (openTask.mWmService.mGlobalLock) {
if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
mStartingSurface = sc;
+ openTask.mWmService.mWindowPlacerLocked
+ .requestTraversal();
} else {
sc.release();
}
@@ -1371,28 +1536,6 @@
});
}
- // When back gesture has triggered and transition target matches navigation target,
- // reparent the starting surface to the opening target as it's starting window.
- void reparentWindowlessSurfaceToTarget(SurfaceControl.Transaction reparentTransaction) {
- if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
- return;
- }
- // If open target matches, reparent to open activity or task
- if (mStartingSurface != null && mStartingSurface.isValid()) {
- SurfaceControl.Transaction transaction = reparentTransaction != null
- ? reparentTransaction : mAdaptors[0].mTarget.getPendingTransaction();
- if (mAdaptors.length != 1) {
- // More than one opening window, reparent starting surface to leaf task.
- final WindowContainer wc = mAdaptors[0].mTarget;
- final Task task = wc.asActivityRecord() != null
- ? wc.asActivityRecord().getTask() : wc.asTask();
- transaction.reparent(mStartingSurface, task != null
- ? task.getSurfaceControl()
- : mAdaptors[0].mTarget.getSurfaceControl());
- }
- }
- }
-
/**
* Ask shell to clear the starting surface.
* @param openTransitionMatch if true, shell will play the remove starting window
@@ -1430,6 +1573,22 @@
mSwitchType = switchType;
}
+ Task getTopTask() {
+ final Task asTask = mTarget.asTask();
+ if (asTask != null) {
+ return asTask;
+ }
+ final ActivityRecord ar = mTarget.asActivityRecord();
+ if (ar != null) {
+ return ar.getTask();
+ }
+ final TaskFragment tf = mTarget.asTaskFragment();
+ if (tf != null) {
+ return tf.getTask();
+ }
+ return null;
+ }
+
@Override
public boolean getShowWallpaper() {
return false;
@@ -1619,9 +1778,8 @@
needsLaunchBehind = snapshot == null;
}
if (needsLaunchBehind) {
- for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
- setLaunchBehind(visibleOpenActivities[i]);
- }
+ openAnimationAdaptor.mPreparedOpenTransition =
+ setLaunchBehind(visibleOpenActivities);
}
// Force update mLastSurfaceShowing for opening activity and its task.
if (mWindowManagerService.mRoot.mTransitionController.isShellTransitionsEnabled()) {
@@ -1677,13 +1835,22 @@
// animation was canceled
return;
}
- if (!triggerBack) {
- clearBackAnimateTarget(true /* cancel */);
+ if (Flags.migratePredictiveBackTransition()) {
+ if (mOpenAnimAdaptor == null
+ || mOpenAnimAdaptor.mPreparedOpenTransition == null) {
+ // no open nor close transition, this is window animation
+ if (!triggerBack) {
+ clearBackAnimateTarget(true /* cancel */);
+ }
+ }
} else {
- mWaitTransition = true;
+ if (!triggerBack) {
+ clearBackAnimateTarget(true /* cancel */);
+ } else {
+ mWaitTransition = true;
+ }
}
}
- // TODO Add timeout monitor if transition didn't happen
}
};
}
@@ -1740,28 +1907,105 @@
return openActivities;
}
- private static void setLaunchBehind(@NonNull ActivityRecord activity) {
- if (!activity.isVisibleRequested()) {
- // The transition could commit the visibility and in the finishing state, that could
- // skip commitVisibility call in setVisibility cause the activity won't visible here.
- // Call it again to make sure the activity could be visible while handling the pending
- // animation.
- // Do not performLayout during prepare animation, because it could cause focus window
- // change. Let that happen after the BackNavigationInfo has returned to shell.
- activity.commitVisibility(true, false /* performLayout */);
- activity.mTransitionController.mSnapshotController
- .mActivitySnapshotController.addOnBackPressedActivity(activity);
+ boolean restoreBackNavigation() {
+ if (!mAnimationHandler.mComposed) {
+ return false;
}
- activity.mLaunchTaskBehind = true;
-
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
- activity.mTaskSupervisor.mStoppingActivities.remove(activity);
- activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
- true /* notifyClients */);
+ 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;
}
- private static void restoreLaunchBehind(@NonNull ActivityRecord activity, boolean cancel) {
+ 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<>();
+ for (int i = activities.length - 1; i >= 0; --i) {
+ final ActivityRecord activity = activities[i];
+ if (activity.mLaunchTaskBehind || activity.isVisibleRequested()) {
+ continue;
+ }
+ affects.add(activity);
+ }
+ if (affects.isEmpty()) {
+ return null;
+ }
+
+ final TransitionController tc = activities[0].mTransitionController;
+ final Transition prepareOpen = migrateBackTransition && !tc.isCollecting()
+ ? tc.createTransition(TRANSIT_PREPARE_BACK_NAVIGATION) : null;
+
+ for (int i = affects.size() - 1; i >= 0; --i) {
+ final ActivityRecord activity = affects.get(i);
+ if (!migrateBackTransition && !activity.isVisibleRequested()) {
+ // The transition could commit the visibility and in the finishing state, that could
+ // skip commitVisibility call in setVisibility cause the activity won't visible
+ // here.
+ // Call it again to make sure the activity could be visible while handling the
+ // pending animation.
+ // Do not performLayout during prepare animation, because it could cause focus
+ // window change. Let that happen after the BackNavigationInfo has returned to
+ // shell.
+ activity.commitVisibility(true, false /* performLayout */);
+ }
+ activity.mTransitionController.mSnapshotController
+ .mActivitySnapshotController.addOnBackPressedActivity(activity);
+ activity.mLaunchTaskBehind = true;
+
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
+ activity.mTaskSupervisor.mStoppingActivities.remove(activity);
+
+ if (!migrateBackTransition) {
+ activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
+ true /* notifyClients */);
+ } else if (activity.shouldBeVisible()) {
+ activity.ensureActivityConfiguration(true /* ignoreVisibility */);
+ activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */);
+ }
+ }
+ if (prepareOpen != null) {
+ if (prepareOpen.hasChanges()) {
+ tc.requestStartTransition(prepareOpen,
+ null /*startTask */, null /* remoteTransition */,
+ null /* displayChange */);
+ prepareOpen.setReady(affects.get(0), true);
+ return prepareOpen;
+ } else {
+ prepareOpen.abort();
+ }
+ }
+ return null;
+ }
+
+ private static void restoreLaunchBehind(@NonNull ActivityRecord activity, boolean cancel,
+ boolean finishTransition) {
if (!activity.isAttached()) {
// The activity was detached from hierarchy.
return;
@@ -1771,9 +2015,18 @@
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
activity);
if (cancel) {
- // Restore the launch-behind state
- // TODO b/347168362 Change status directly during collecting for a transition.
- activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
+ final boolean migrateBackTransition = Flags.migratePredictiveBackTransition();
+ // 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.
+ activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
+ }
// Ignore all change
activity.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
@@ -1835,12 +2088,7 @@
&& ah.mSwitchType != AnimationHandler.ACTIVITY_SWITCH)) {
return;
}
- for (int i = mAnimationHandler.mOpenActivities.length - 1; i >= 0; --i) {
- final ActivityRecord preDrawActivity = mAnimationHandler.mOpenActivities[i];
- if (!preDrawActivity.mLaunchTaskBehind) {
- setLaunchBehind(preDrawActivity);
- }
- }
+ setLaunchBehind(mAnimationHandler.mOpenActivities);
}
}
}
@@ -1853,16 +2101,21 @@
snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
task.mTaskId, task.mUserId, false /* restoreFromDisk */,
false /* isLowResolution */);
- } else if (w.asActivityRecord() != null) {
- final ActivityRecord ar = w.asActivityRecord();
- snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
- .getSnapshot(visibleOpenActivities);
+ } else {
+ ActivityRecord ar = w.asActivityRecord();
+ if (ar == null && w.asTaskFragment() != null) {
+ ar = w.asTaskFragment().getTopNonFinishingActivity();
+ }
+ if (ar != null) {
+ snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
+ .getSnapshot(visibleOpenActivities);
+ }
}
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;
@@ -1873,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 9996bbc..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();
@@ -234,12 +241,13 @@
float desiredAspectRatio = 0;
if (taskInfo.isRunning) {
final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
- if (appCompatTaskInfo.topActivityBoundsLetterboxed) {
- desiredAspectRatio = (float) Math.max(
- appCompatTaskInfo.topActivityLetterboxWidth,
- appCompatTaskInfo.topActivityLetterboxHeight)
- / Math.min(appCompatTaskInfo.topActivityLetterboxWidth,
- appCompatTaskInfo.topActivityLetterboxHeight);
+ final int appLetterboxWidth =
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth;
+ final int appLetterboxHeight =
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight;
+ if (appCompatTaskInfo.isTopActivityLetterboxed()) {
+ desiredAspectRatio = (float) Math.max(appLetterboxWidth, appLetterboxHeight)
+ / Math.min(appLetterboxWidth, appLetterboxHeight);
} else {
desiredAspectRatio = Math.max(fullscreenHeight, fullscreenWidth)
/ Math.min(fullscreenHeight, fullscreenWidth);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3a0de85..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;
}
@@ -4415,13 +4413,14 @@
mWmService.dispatchImeInputTargetVisibilityChanged(
targetWin.mClient.asBinder(), isVisibleRequested,
targetWin.mActivityRecord != null
- && targetWin.mActivityRecord.finishing);
+ && targetWin.mActivityRecord.finishing,
+ mDisplayId);
}
});
targetWin.mToken.registerWindowContainerListener(
mImeTargetTokenListenerPair.second);
mWmService.dispatchImeInputTargetVisibilityChanged(targetWin.mClient.asBinder(),
- targetWin.isVisible() /* visible */, false /* removed */);
+ targetWin.isVisible() /* visible */, false /* removed */, mDisplayId);
}
}
if (refreshImeSecureFlag(getPendingTransaction())) {
@@ -6709,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/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 4ec318b..dedf35a 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -24,6 +24,7 @@
import android.companion.virtualdevice.flags.Flags;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.os.Process;
import android.os.UserHandle;
@@ -34,6 +35,7 @@
import java.io.PrintWriter;
import java.util.List;
+import java.util.function.Supplier;
class DisplayWindowPolicyControllerHelper {
private static final String TAG = "DisplayWindowPolicyControllerHelper";
@@ -98,7 +100,8 @@
*/
public boolean canActivityBeLaunched(ActivityInfo activityInfo,
Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
- int launchingFromDisplayId, boolean isNewTask) {
+ int launchingFromDisplayId, boolean isNewTask, boolean isResultExpected,
+ Supplier<IntentSender> intentSender) {
if (mDisplayWindowPolicyController == null) {
// Missing controller means that this display has no categories for activity launch
// restriction.
@@ -111,7 +114,7 @@
return true;
}
return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, intent,
- windowingMode, launchingFromDisplayId, isNewTask);
+ windowingMode, launchingFromDisplayId, isNewTask, isResultExpected, intentSender);
}
private boolean hasDisplayCategory(ActivityInfo aInfo) {
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/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 27e6e09..7135c3b 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -44,7 +44,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider;
-import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -145,9 +144,6 @@
* @see #DATA_DISPLAY_SETTINGS_FILE_PATH
*/
void setOverrideSettingsForUser(@UserIdInt int userId) {
- if (!Flags.perUserDisplayWindowSettings()) {
- return;
- }
final AtomicFile settingsFile = getOverrideSettingsFileForUser(userId);
setOverrideSettingsStorage(new AtomicFileStorage(settingsFile));
}
@@ -165,9 +161,6 @@
*/
void removeStaleDisplaySettingsLocked(@NonNull WindowManagerService wms,
@NonNull RootWindowContainer root) {
- if (!Flags.perUserDisplayWindowSettings()) {
- return;
- }
final Set<String> displayIdentifiers = new ArraySet<>();
final Consumer<DisplayInfo> addDisplayIdentifier =
displayInfo -> displayIdentifiers.add(mOverrideSettings.getIdentifier(displayInfo));
@@ -403,12 +396,9 @@
@NonNull
private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) {
- final File directory;
- if (userId == USER_SYSTEM || !Flags.perUserDisplayWindowSettings()) {
- directory = Environment.getDataDirectory();
- } else {
- directory = Environment.getDataSystemCeDirectory(userId);
- }
+ final File directory = (userId == USER_SYSTEM)
+ ? Environment.getDataDirectory()
+ : Environment.getDataSystemCeDirectory(userId);
final File overrideSettingsFile = new File(directory, DATA_DISPLAY_SETTINGS_FILE_PATH);
return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG);
}
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 88b76aa..0000000
--- a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
+++ /dev/null
@@ -1,61 +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.
- */
- default void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType,
- boolean visible, boolean removed) {
- }
-
- /**
- * 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 {@param 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.
- */
- default void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget, boolean visible,
- boolean removed) {
- }
-}
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/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 2aa7c0c..3fc5eaf 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -40,7 +40,6 @@
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
-import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
@@ -76,9 +75,8 @@
// for overlaping an app window and letterbox surfaces.
private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
- // Reachability gestures.
- private final IntConsumer mDoubleTapCallbackX;
- private final IntConsumer mDoubleTapCallbackY;
+ @NonNull
+ private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
/**
* Constructs a Letterbox.
@@ -92,8 +90,7 @@
BooleanSupplier hasWallpaperBackgroundSupplier,
IntSupplier blurRadiusSupplier,
DoubleSupplier darkScrimAlphaSupplier,
- IntConsumer doubleTapCallbackX,
- IntConsumer doubleTapCallbackY,
+ @NonNull AppCompatReachabilityPolicy appCompatReachabilityPolicy,
Supplier<SurfaceControl> parentSurface) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
@@ -102,9 +99,10 @@
mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier;
mBlurRadiusSupplier = blurRadiusSupplier;
mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
- mDoubleTapCallbackX = doubleTapCallbackX;
- mDoubleTapCallbackY = doubleTapCallbackY;
+ mAppCompatReachabilityPolicy = appCompatReachabilityPolicy;
mParentSurfaceSupplier = parentSurface;
+ // TODO Remove after Letterbox refactoring.
+ mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(this::getInnerFrame);
}
/**
@@ -290,8 +288,8 @@
// This check prevents late events to be handled in case the Letterbox has been
// already destroyed and so mOuter.isEmpty() is true.
if (!mOuter.isEmpty() && e.getAction() == MotionEvent.ACTION_UP) {
- mDoubleTapCallbackX.accept((int) e.getRawX());
- mDoubleTapCallbackY.accept((int) e.getRawY());
+ mAppCompatReachabilityPolicy.handleDoubleTap((int) e.getRawX(),
+ (int) e.getRawY());
return true;
}
return false;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 291eab1..4740fc4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -16,43 +16,21 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
-import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
-import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
-import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
import static com.android.server.wm.AppCompatConfiguration.letterboxBackgroundTypeToString;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
-import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -68,7 +46,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.AppCompatConfiguration.LetterboxBackgroundType;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -85,6 +62,16 @@
private final ActivityRecord mActivityRecord;
+ // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
+ @NonNull
+ private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
+ @NonNull
+ private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+ @NonNull
+ private final TransparentPolicy mTransparentPolicy;
+ @NonNull
+ private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
+
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
@@ -92,14 +79,21 @@
private boolean mLastShouldShowLetterboxUi;
- private boolean mDoubleTapEvent;
-
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mAppCompatConfiguration = wmService.mAppCompatConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
+ // TODO(b/356385137): Remove these we added to make dependencies temporarily explicit.
+ mAppCompatReachabilityOverrides = mActivityRecord.mAppCompatController
+ .getAppCompatReachabilityOverrides();
+ mAppCompatReachabilityPolicy = mActivityRecord.mAppCompatController
+ .getAppCompatReachabilityPolicy();
+ mTransparentPolicy = mActivityRecord.mAppCompatController.getTransparentPolicy();
+ mAppCompatOrientationOverrides = mActivityRecord.mAppCompatController
+ .getAppCompatOrientationOverrides();
+
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -107,6 +101,8 @@
if (mLetterbox != null) {
mLetterbox.destroy();
mLetterbox = null;
+ // TODO Remove after Letterbox refactoring.
+ mAppCompatReachabilityPolicy.setLetterboxInnerBoundsSupplier(null);
}
}
@@ -190,8 +186,7 @@
this::hasWallpaperBackgroundForLetterbox,
this::getLetterboxWallpaperBlurRadiusPx,
this::getLetterboxWallpaperDarkScrimAlpha,
- this::handleHorizontalDoubleTap,
- this::handleVerticalDoubleTap,
+ mAppCompatReachabilityPolicy,
this::getLetterboxParentSurface);
mLetterbox.attachInput(w);
}
@@ -227,11 +222,10 @@
// For this reason we use ActivityRecord#getBounds() that the translucent activity
// inherits from the first opaque activity beneath and also takes care of the scaling
// in case of activities in size compat mode.
- final Rect innerFrame = mActivityRecord.mAppCompatController
- .getTransparentPolicy().isRunning()
- ? mActivityRecord.getBounds() : w.getFrame();
+ final Rect innerFrame =
+ mTransparentPolicy.isRunning() ? mActivityRecord.getBounds() : w.getFrame();
mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
- if (mDoubleTapEvent) {
+ if (mAppCompatReachabilityOverrides.isDoubleTapEvent()) {
// We need to notify Shell that letterbox position has changed.
mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
}
@@ -240,12 +234,6 @@
}
}
- boolean isFromDoubleTap() {
- final boolean isFromDoubleTap = mDoubleTapEvent;
- mDoubleTapEvent = false;
- return isFromDoubleTap;
- }
-
SurfaceControl getLetterboxParentSurface() {
if (mActivityRecord.isInLetterboxAnimation()) {
return mActivityRecord.getTask().getSurfaceControl();
@@ -272,310 +260,13 @@
&& mActivityRecord.fillsParent();
}
- // Check if we are in the given pose and in fullscreen mode.
- // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
- // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
- // actually fullscreen. If display is still in transition e.g. unfolding, don't return true
- // for HALF_FOLDED state or app will flicker.
- boolean isDisplayFullScreenAndInPosture(boolean isTabletop) {
- Task task = mActivityRecord.getTask();
- return mActivityRecord.mDisplayContent != null && task != null
- && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(
- DeviceStateController.DeviceState.HALF_FOLDED, isTabletop)
- && !mActivityRecord.mDisplayContent.inTransition()
- && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
- }
-
- // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
- // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
- // actually fullscreen.
- private boolean isDisplayFullScreenAndSeparatingHinge() {
- Task task = mActivityRecord.getTask();
- return mActivityRecord.mDisplayContent != null
- && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
- && task != null
- && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
- }
-
-
- float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
- // Don't check resolved configuration because it may not be updated yet during
- // configuration change.
- boolean bookModeEnabled = isFullScreenAndBookModeEnabled();
- return isHorizontalReachabilityEnabled(parentConfiguration)
- // Using the last global dynamic position to avoid "jumps" when moving
- // between apps or activities.
- ? mAppCompatConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled)
- : mAppCompatConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled);
- }
-
- private boolean isFullScreenAndBookModeEnabled() {
- return isDisplayFullScreenAndInPosture(/* isTabletop */ false)
- && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
- }
-
- float getVerticalPositionMultiplier(Configuration parentConfiguration) {
- // Don't check resolved configuration because it may not be updated yet during
- // configuration change.
- boolean tabletopMode = isDisplayFullScreenAndInPosture(/* isTabletop */ true);
- return isVerticalReachabilityEnabled(parentConfiguration)
- // Using the last global dynamic position to avoid "jumps" when moving
- // between apps or activities.
- ? mAppCompatConfiguration.getVerticalMultiplierForReachability(tabletopMode)
- : mAppCompatConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
- }
-
boolean isLetterboxEducationEnabled() {
return mAppCompatConfiguration.getIsEducationEnabled();
}
- /**
- * @return {@value true} if the resulting app is letterboxed in a way defined as thin.
- */
- boolean isVerticalThinLetterboxed() {
- final int thinHeight = mAppCompatConfiguration.getThinLetterboxHeightPx();
- if (thinHeight < 0) {
- return false;
- }
- final Task task = mActivityRecord.getTask();
- if (task == null) {
- return false;
- }
- final int padding = Math.abs(
- task.getBounds().height() - mActivityRecord.getBounds().height()) / 2;
- return padding <= thinHeight;
- }
-
- /**
- * @return {@value true} if the resulting app is pillarboxed in a way defined as thin.
- */
- boolean isHorizontalThinLetterboxed() {
- final int thinWidth = mAppCompatConfiguration.getThinLetterboxWidthPx();
- if (thinWidth < 0) {
- return false;
- }
- final Task task = mActivityRecord.getTask();
- if (task == null) {
- return false;
- }
- final int padding = Math.abs(
- task.getBounds().width() - mActivityRecord.getBounds().width()) / 2;
- return padding <= thinWidth;
- }
-
-
- /**
- * @return {@value true} if the vertical reachability should be allowed in case of
- * thin letteboxing
- */
- boolean allowVerticalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingPolicy()) {
- return true;
- }
- // When the flag is enabled we allow vertical reachability only if the
- // app is not thin letterboxed vertically.
- return !isVerticalThinLetterboxed();
- }
-
- /**
- * @return {@value true} if the vertical reachability should be enabled in case of
- * thin letteboxing
- */
- boolean allowHorizontalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingPolicy()) {
- return true;
- }
- // When the flag is enabled we allow horizontal reachability only if the
- // app is not thin pillarboxed.
- return !isHorizontalThinLetterboxed();
- }
-
- boolean shouldOverrideMinAspectRatio() {
- return mActivityRecord.mAppCompatController.getAppCompatAspectRatioOverrides()
- .shouldOverrideMinAspectRatio();
- }
-
- @AppCompatConfiguration.LetterboxVerticalReachabilityPosition
- int getLetterboxPositionForVerticalReachability() {
- final boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge();
- return mAppCompatConfiguration.getLetterboxPositionForVerticalReachability(
- isInFullScreenTabletopMode);
- }
-
- @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition
- int getLetterboxPositionForHorizontalReachability() {
- final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled();
- return mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(
- isInFullScreenBookMode);
- }
-
- @VisibleForTesting
- void handleHorizontalDoubleTap(int x) {
- if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
- return;
- }
-
- if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) {
- // Only react to clicks at the sides of the letterboxed app window.
- return;
- }
-
- boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge()
- && mAppCompatConfiguration.getIsAutomaticReachabilityInBookModeEnabled();
- int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
- .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
- if (mLetterbox.getInnerFrame().left > x) {
- // Moving to the next stop on the left side of the app window: right > center > left.
- mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextLeftStop(
- isInFullScreenBookMode);
- int changeToLog =
- letterboxPositionForHorizontalReachability
- == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- } else if (mLetterbox.getInnerFrame().right < x) {
- // Moving to the next stop on the right side of the app window: left > center > right.
- mAppCompatConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
- isInFullScreenBookMode);
- int changeToLog =
- letterboxPositionForHorizontalReachability
- == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- }
- // TODO(197549949): Add animation for transition.
- mActivityRecord.recomputeConfiguration();
- }
-
- @VisibleForTesting
- void handleVerticalDoubleTap(int y) {
- if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
- return;
- }
-
- if (mLetterbox.getInnerFrame().top <= y && mLetterbox.getInnerFrame().bottom >= y) {
- // Only react to clicks at the top and bottom of the letterboxed app window.
- return;
- }
- boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge();
- int letterboxPositionForVerticalReachability = mAppCompatConfiguration
- .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode);
- if (mLetterbox.getInnerFrame().top > y) {
- // Moving to the next stop on the top side of the app window: bottom > center > top.
- mAppCompatConfiguration.movePositionForVerticalReachabilityToNextTopStop(
- isInFullScreenTabletopMode);
- int changeToLog =
- letterboxPositionForVerticalReachability
- == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- } else if (mLetterbox.getInnerFrame().bottom < y) {
- // Moving to the next stop on the bottom side of the app window: top > center > bottom.
- mAppCompatConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
- isInFullScreenTabletopMode);
- int changeToLog =
- letterboxPositionForVerticalReachability
- == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
- ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
- : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
- logLetterboxPositionChange(changeToLog);
- mDoubleTapEvent = true;
- }
- // TODO(197549949): Add animation for transition.
- mActivityRecord.recomputeConfiguration();
- }
-
- /**
- * Whether horizontal reachability is enabled for an activity in the current configuration.
- *
- * <p>Conditions that needs to be met:
- * <ul>
- * <li>Windowing mode is fullscreen.
- * <li>Horizontal Reachability is enabled.
- * <li>First top opaque activity fills parent vertically, but not horizontally.
- * </ul>
- */
- private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
- if (!allowHorizontalReachabilityForThinLetterbox()) {
- return false;
- }
- final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
- final Rect parentAppBounds = parentAppBoundsOverride != null
- ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
- // Use screen resolved bounds which uses resolved bounds or size compat bounds
- // as activity bounds can sometimes be empty
- final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
- .getTransparentPolicy().getFirstOpaqueActivity()
- .map(ActivityRecord::getScreenResolvedBounds)
- .orElse(mActivityRecord.getScreenResolvedBounds());
- return mAppCompatConfiguration.getIsHorizontalReachabilityEnabled()
- && parentConfiguration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN
- // Check whether the activity fills the parent vertically.
- && parentAppBounds.height() <= opaqueActivityBounds.height()
- && parentAppBounds.width() > opaqueActivityBounds.width();
- }
-
- @VisibleForTesting
- boolean isHorizontalReachabilityEnabled() {
- return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
- }
-
- boolean isLetterboxDoubleTapEducationEnabled() {
- return isHorizontalReachabilityEnabled() || isVerticalReachabilityEnabled();
- }
-
- // TODO(b/346264992): Remove after AppCompatController refactoring
- private AppCompatOverrides getAppCompatOverrides() {
- return mActivityRecord.mAppCompatController.getAppCompatOverrides();
- }
-
- /**
- * Whether vertical reachability is enabled for an activity in the current configuration.
- *
- * <p>Conditions that needs to be met:
- * <ul>
- * <li>Windowing mode is fullscreen.
- * <li>Vertical Reachability is enabled.
- * <li>First top opaque activity fills parent horizontally but not vertically.
- * </ul>
- */
- private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
- if (!allowVerticalReachabilityForThinLetterbox()) {
- return false;
- }
- final Rect parentAppBoundsOverride = mActivityRecord.getParentAppBoundsOverride();
- final Rect parentAppBounds = parentAppBoundsOverride != null
- ? parentAppBoundsOverride : parentConfiguration.windowConfiguration.getAppBounds();
- // Use screen resolved bounds which uses resolved bounds or size compat bounds
- // as activity bounds can sometimes be empty.
- final Rect opaqueActivityBounds = mActivityRecord.mAppCompatController
- .getTransparentPolicy().getFirstOpaqueActivity()
- .map(ActivityRecord::getScreenResolvedBounds)
- .orElse(mActivityRecord.getScreenResolvedBounds());
- return mAppCompatConfiguration.getIsVerticalReachabilityEnabled()
- && parentConfiguration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN
- // Check whether the activity fills the parent horizontally.
- && parentAppBounds.width() <= opaqueActivityBounds.width()
- && parentAppBounds.height() > opaqueActivityBounds.height();
- }
-
- @VisibleForTesting
- boolean isVerticalReachabilityEnabled() {
- return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
- }
-
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- if (getAppCompatOverrides().getAppCompatOrientationOverrides()
- .getIsRelaunchingAfterRequestedOrientationChanged()) {
+ if (mAppCompatOrientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) {
return mLastShouldShowLetterboxUi;
}
@@ -664,8 +355,7 @@
// corners because we assume the specific layout would. This is the case when the layout
// of the translucent activity uses only a part of all the bounds because of the use of
// LayoutParams.WRAP_CONTENT.
- if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()
- && (cropBounds.width() != mainWindow.mRequestedWidth
+ if (mTransparentPolicy.isRunning() && (cropBounds.width() != mainWindow.mRequestedWidth
|| cropBounds.height() != mainWindow.mRequestedHeight)) {
return null;
}
@@ -802,13 +492,17 @@
return;
}
+ pw.println(prefix + "isTransparentPolicyRunning="
+ + mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning());
+
boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed();
pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed);
if (!areBoundsLetterboxed) {
return;
}
- pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin));
+ pw.println(prefix + " letterboxReason="
+ + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin));
pw.println(prefix + " activityAspectRatio="
+ AppCompatUtils.computeAspectRatio(mActivityRecord.getBounds()));
@@ -818,8 +512,10 @@
if (!shouldShowLetterboxUi) {
return;
}
- pw.println(prefix + " isVerticalThinLetterboxed=" + isVerticalThinLetterboxed());
- pw.println(prefix + " isHorizontalThinLetterboxed=" + isHorizontalThinLetterboxed());
+ pw.println(prefix + " isVerticalThinLetterboxed="
+ + mAppCompatReachabilityOverrides.isVerticalThinLetterboxed());
+ pw.println(prefix + " isHorizontalThinLetterboxed="
+ + mAppCompatReachabilityOverrides.isHorizontalThinLetterboxed());
pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString(
getLetterboxBackgroundColor().toArgb()));
pw.println(prefix + " letterboxBackgroundType="
@@ -836,14 +532,18 @@
pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius="
+ getLetterboxWallpaperBlurRadiusPx());
}
-
+ final AppCompatReachabilityOverrides reachabilityOverrides = mActivityRecord
+ .mAppCompatController.getAppCompatReachabilityOverrides();
pw.println(prefix + " isHorizontalReachabilityEnabled="
- + isHorizontalReachabilityEnabled());
- pw.println(prefix + " isVerticalReachabilityEnabled=" + isVerticalReachabilityEnabled());
+ + reachabilityOverrides.isHorizontalReachabilityEnabled());
+ pw.println(prefix + " isVerticalReachabilityEnabled="
+ + reachabilityOverrides.isVerticalReachabilityEnabled());
pw.println(prefix + " letterboxHorizontalPositionMultiplier="
- + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ + mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(mActivityRecord
+ .getParent().getConfiguration()));
pw.println(prefix + " letterboxVerticalPositionMultiplier="
- + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ + mAppCompatReachabilityOverrides.getVerticalPositionMultiplier(mActivityRecord
+ .getParent().getConfiguration()));
pw.println(prefix + " letterboxPositionForHorizontalReachability="
+ AppCompatConfiguration.letterboxHorizontalReachabilityPositionToString(
mAppCompatConfiguration.getLetterboxPositionForHorizontalReachability(false)));
@@ -861,86 +561,6 @@
.getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
}
- /**
- * Returns a string representing the reason for letterboxing. This method assumes the activity
- * is letterboxed.
- */
- private String getLetterboxReasonString(WindowState mainWin) {
- if (mActivityRecord.inSizeCompatMode()) {
- return "SIZE_COMPAT_MODE";
- }
- if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
- .isLetterboxedForFixedOrientationAndAspectRatio()) {
- return "FIXED_ORIENTATION";
- }
- if (mainWin.isLetterboxedForDisplayCutout()) {
- return "DISPLAY_CUTOUT";
- }
- if (mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
- .isLetterboxedForAspectRatioOnly()) {
- return "ASPECT_RATIO";
- }
- return "UNKNOWN_REASON";
- }
-
- private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
- @AppCompatConfiguration.LetterboxHorizontalReachabilityPosition int position) {
- switch (position) {
- case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
- case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
- case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
- default:
- throw new AssertionError(
- "Unexpected letterbox horizontal reachability position type: "
- + position);
- }
- }
-
- private int letterboxVerticalReachabilityPositionToLetterboxPosition(
- @AppCompatConfiguration.LetterboxVerticalReachabilityPosition int position) {
- switch (position) {
- case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
- case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
- case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
- return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
- default:
- throw new AssertionError(
- "Unexpected letterbox vertical reachability position type: "
- + position);
- }
- }
-
- int getLetterboxPositionForLogging() {
- int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
- if (isHorizontalReachabilityEnabled()) {
- int letterboxPositionForHorizontalReachability = mAppCompatConfiguration
- .getLetterboxPositionForHorizontalReachability(
- isDisplayFullScreenAndInPosture(/* isTabletop */ false));
- positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
- letterboxPositionForHorizontalReachability);
- } else if (isVerticalReachabilityEnabled()) {
- int letterboxPositionForVerticalReachability = mAppCompatConfiguration
- .getLetterboxPositionForVerticalReachability(
- isDisplayFullScreenAndInPosture(/* isTabletop */ true));
- positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
- letterboxPositionForVerticalReachability);
- }
- return positionToLog;
- }
-
- /**
- * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
- */
- private void logLetterboxPositionChange(int letterboxPositionChange) {
- mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
- .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
- }
-
@Nullable
LetterboxDetails getLetterboxDetails() {
final WindowState w = mActivityRecord.findMainWindow();
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/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7ada4c7..d3df5fd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3389,7 +3389,6 @@
info.isTopActivityTransparent = top != null && !top.fillsParent();
info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
-
AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
}
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 996b439..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) {
@@ -1764,7 +1768,7 @@
// Check whether the participants were animated from back navigation.
mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
- transaction);
+ transaction, mFinishTransaction);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
info.setDebugId(mSyncId);
mController.assignTrack(this, info);
@@ -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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 2652670..6ea1f3a 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -1064,13 +1064,17 @@
/**
* Mirrors the visible wallpaper if it's available.
+ * <p>
+ * We mirror at the WallpaperWindowToken level because scale and translation is applied at
+ * the WindowState level and mirroring the WindowState's SurfaceControl will remove any local
+ * scale and translation.
*
* @return A SurfaceControl for the parent of the mirrored wallpaper.
*/
SurfaceControl mirrorWallpaperSurface() {
final WindowState wallpaperWindowState = getTopVisibleWallpaper();
return wallpaperWindowState != null
- ? SurfaceControl.mirrorSurface(wallpaperWindowState.getSurfaceControl())
+ ? SurfaceControl.mirrorSurface(wallpaperWindowState.mToken.getSurfaceControl())
: null;
}
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 d73d509..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;
@@ -1897,7 +1896,8 @@
displayContent.computeImeTarget(true /* updateImeTarget */);
if (win.isImeOverlayLayeringTarget()) {
dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
- win.isVisibleRequestedOrAdding(), false /* removed */);
+ win.isVisibleRequestedOrAdding(), false /* removed */,
+ displayContent.getDisplayId());
}
}
@@ -2661,13 +2661,13 @@
final boolean winVisibleChanged = win.isVisible() != wasVisible;
if (win.isImeOverlayLayeringTarget() && winVisibleChanged) {
dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
- win.isVisible(), false /* removed */);
+ win.isVisible(), false /* removed */, win.getDisplayId());
}
// Notify listeners about IME input target window visibility change.
final boolean isImeInputTarget = win.getDisplayContent().getImeInputTarget() == win;
if (isImeInputTarget && winVisibleChanged) {
dispatchImeInputTargetVisibilityChanged(win.mClient.asBinder(),
- win.isVisible() /* visible */, false /* removed */);
+ win.isVisible() /* visible */, false /* removed */, win.getDisplayId());
}
if (outRelayoutResult != null) {
@@ -3515,28 +3515,29 @@
void dispatchImeTargetOverlayVisibilityChanged(@NonNull IBinder token,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
- 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);
- }
- mH.post(() -> mImeTargetChangeListener.onImeTargetOverlayVisibilityChanged(token,
- windowType, visible, removed));
+ boolean removed, int 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) {
- if (mImeTargetChangeListener != null) {
- if (DEBUG_INPUT_METHOD) {
- Slog.d(TAG, "onImeInputTargetVisibilityChanged, win=" + mWindowMap.get(token)
- + "visible=" + visible + ", removed=" + removed);
- }
- mH.post(() -> mImeTargetChangeListener.onImeInputTargetVisibilityChanged(token,
- visible, removed));
+ boolean removed, int 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
@@ -8672,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/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 51d5bc0..092a751 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -284,30 +284,28 @@
private int runDisplayDensity(PrintWriter pw) throws RemoteException {
String densityStr = getNextArg();
- String option = getNextOption();
String arg = getNextArg();
int density;
int displayId = Display.DEFAULT_DISPLAY;
- if ("-d".equals(option) && arg != null) {
+ if ("-d".equals(densityStr) && arg != null) {
try {
displayId = Integer.parseInt(arg);
} catch (NumberFormatException e) {
getErrPrintWriter().println("Error: bad number " + e);
}
- } else if ("-u".equals(option) && arg != null) {
+ densityStr = getNextArg();
+ } else if ("-u".equals(densityStr) && arg != null) {
displayId = mInterface.getDisplayIdByUniqueId(arg);
if (displayId == Display.INVALID_DISPLAY) {
getErrPrintWriter().println("Error: the uniqueId is invalid ");
return -1;
}
+ densityStr = getNextArg();
}
if (densityStr == null) {
printInitialDisplayDensity(pw, displayId);
return 0;
- } else if ("-d".equals(densityStr)) {
- printInitialDisplayDensity(pw, displayId);
- return 0;
} else if ("reset".equals(densityStr)) {
density = -1;
} else {
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 153d41b..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;
@@ -2359,11 +2360,11 @@
}
super.removeImmediately();
+ final DisplayContent dc = getDisplayContent();
if (isImeOverlayLayeringTarget()) {
mWmService.dispatchImeTargetOverlayVisibilityChanged(mClient.asBinder(), mAttrs.type,
- false /* visible */, true /* removed */);
+ false /* visible */, true /* removed */, dc.getDisplayId());
}
- final DisplayContent dc = getDisplayContent();
if (isImeLayeringTarget()) {
// Remove the attached IME screenshot surface.
dc.removeImeSurfaceByTarget(this);
@@ -2374,7 +2375,7 @@
}
if (dc.getImeInputTarget() == this && !inRelaunchingActivity()) {
mWmService.dispatchImeInputTargetVisibilityChanged(mClient.asBinder(),
- false /* visible */, true /* removed */);
+ false /* visible */, true /* removed */, dc.getDisplayId());
dc.updateImeInputAndControlTarget(null);
}
@@ -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.
@@ -4415,6 +4435,11 @@
for (int i = mChildren.size() - 1; i >= 0; i--) {
committed |= mChildren.get(i).commitFinishDrawing(t);
}
+ // In case commitFinishDrawingLocked starts a window level animation, make sure the surface
+ // operation (reparent to leash) is synced with the visibility by transition.
+ if (getAnimationLeash() != null) {
+ t.merge(getSyncTransaction());
+ }
return committed;
}
@@ -5028,7 +5053,9 @@
new WindowAnimationSpec(anim, position, false /* canSkipFirstFrame */,
0 /* windowCornerRadius */),
mWmService.mSurfaceAnimationRunner);
- startAnimation(getPendingTransaction(), adapter);
+ final Transaction t = mActivityRecord != null
+ ? getSyncTransaction() : getPendingTransaction();
+ startAnimation(t, adapter);
commitPendingTransaction();
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 9fa1a53..f1e94de 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -79,6 +79,7 @@
"com_android_server_wm_TaskFpsCallbackController.cpp",
"onload.cpp",
":lib_cachedAppOptimizer_native",
+ ":lib_freezer_native",
":lib_gameManagerService_native",
":lib_oomConnection_native",
":lib_anrTimer_native",
@@ -241,6 +242,13 @@
}
filegroup {
+ name: "lib_freezer_native",
+ srcs: [
+ "com_android_server_am_Freezer.cpp",
+ ],
+}
+
+filegroup {
name: "lib_gameManagerService_native",
srcs: [
"com_android_server_app_GameManagerService.cpp",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 1667e27..b622751 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -27,7 +27,8 @@
per-file com_android_server_security_* = file:/core/java/android/security/OWNERS
per-file com_android_server_tv_* = file:/media/java/android/media/tv/OWNERS
per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
+per-file com_android_server_am_CachedAppOptimizer.cpp = file:/PERFORMANCE_OWNERS
+per-file com_android_server_am_Freezer.cpp = file:/PERFORMANCE_OWNERS
per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
# Memory
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 95e7b19..a91fd08 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -24,7 +24,6 @@
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <android_runtime/AndroidRuntime.h>
-#include <binder/IPCThreadState.h>
#include <cutils/compiler.h>
#include <dirent.h>
#include <jni.h>
@@ -34,7 +33,6 @@
#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <nativehelper/JNIHelp.h>
-#include <processgroup/processgroup.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
@@ -63,10 +61,6 @@
using VmaToAdviseFunc = std::function<int(const Vma&)>;
using android::base::unique_fd;
-#define SYNC_RECEIVED_WHILE_FROZEN (1)
-#define ASYNC_RECEIVED_WHILE_FROZEN (2)
-#define TXNS_PENDING_WHILE_FROZEN (4)
-
#define MAX_RW_COUNT (INT_MAX & kPageMask)
// Defines the maximum amount of VMAs we can send per process_madvise syscall.
@@ -527,58 +521,6 @@
compactProcessOrFallback(pid, compactionFlags);
}
-static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
- jint pid, jboolean freeze,
- jint timeout_ms) {
- jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
- if (retVal != 0 && retVal != -EAGAIN) {
- jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
- }
-
- return retVal;
-}
-
-static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
- jobject clazz, jint pid) {
- uint32_t syncReceived = 0, asyncReceived = 0;
-
- int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
-
- if (error < 0) {
- jniThrowException(env, "java/lang/RuntimeException", strerror(error));
- }
-
- jint retVal = 0;
-
- // bit 0 of sync_recv goes to bit 0 of retVal
- retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
- // bit 0 of async_recv goes to bit 1 of retVal
- retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
- // bit 1 of sync_recv goes to bit 2 of retVal
- retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
-
- return retVal;
-}
-
-static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env,
- jobject clazz) {
- std::string path;
-
- if (!getAttributePathForTask("FreezerState", getpid(), &path)) {
- path = "";
- }
-
- return env->NewStringUTF(path.c_str());
-}
-
-static jboolean com_android_server_am_CachedAppOptimizer_isFreezerProfileValid(JNIEnv* env) {
- uid_t uid = getuid();
- pid_t pid = getpid();
-
- return isProfileValidForProcess("Frozen", uid, pid) &&
- isProfileValidForProcess("Unfrozen", uid, pid);
-}
-
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"cancelCompaction", "()V",
@@ -592,13 +534,7 @@
(void*)com_android_server_am_CachedAppOptimizer_getMemoryFreedCompaction},
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
- {"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
- {"getBinderFreezeInfo", "(I)I",
- (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
- {"getFreezerCheckPath", "()Ljava/lang/String;",
- (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath},
- {"isFreezerProfileValid", "()Z",
- (void*)com_android_server_am_CachedAppOptimizer_isFreezerProfileValid}};
+};
int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
{
diff --git a/services/core/jni/com_android_server_am_Freezer.cpp b/services/core/jni/com_android_server_am_Freezer.cpp
new file mode 100644
index 0000000..8148728
--- /dev/null
+++ b/services/core/jni/com_android_server_am_Freezer.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Freezer"
+//#define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_ACTIVITY_MANAGER
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <binder/IPCThreadState.h>
+#include <nativehelper/JNIHelp.h>
+#include <processgroup/processgroup.h>
+
+namespace android {
+namespace {
+
+// Binder status bit flags.
+static const int SYNC_RECEIVED_WHILE_FROZEN = 1;
+static const int ASYNC_RECEIVED_WHILE_FROZEN = 2;
+static const int TXNS_PENDING_WHILE_FROZEN = 4;
+
+jint freezeBinder(JNIEnv* env, jobject, jint pid, jboolean freeze, jint timeout_ms) {
+ jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
+ if (retVal != 0 && retVal != -EAGAIN) {
+ jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
+ }
+
+ return retVal;
+}
+
+jint getBinderFreezeInfo(JNIEnv *env, jobject, jint pid) {
+ uint32_t syncReceived = 0, asyncReceived = 0;
+
+ int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
+
+ if (error < 0) {
+ jniThrowException(env, "java/lang/RuntimeException", strerror(error));
+ }
+
+ jint retVal = 0;
+
+ // bit 0 of sync_recv goes to bit 0 of retVal
+ retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
+ // bit 0 of async_recv goes to bit 1 of retVal
+ retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
+ // bit 1 of sync_recv goes to bit 2 of retVal
+ retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
+
+ return retVal;
+}
+
+bool isFreezerSupported(JNIEnv *env, jclass) {
+ std::string path;
+ if (!getAttributePathForTask("FreezerState", getpid(), &path)) {
+ ALOGI("No attribute for FreezerState");
+ return false;
+ }
+ base::unique_fd fid(open(path.c_str(), O_RDONLY));
+ if (fid < 0) {
+ ALOGI("Cannot open freezer path \"%s\": %s", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ char state;
+ if (::read(fid, &state, 1) != 1) {
+ ALOGI("Failed to read freezer state: %s", strerror(errno));
+ return false;
+ }
+ if (state != '1' && state != '0') {
+ ALOGE("Unexpected value in cgroup.freeze: %d", state);
+ return false;
+ }
+
+ uid_t uid = getuid();
+ pid_t pid = getpid();
+
+ uint32_t syncReceived = 0, asyncReceived = 0;
+ int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
+ if (error < 0) {
+ ALOGE("Unable to read freezer info: %s", strerror(errno));
+ return false;
+ }
+
+ if (!isProfileValidForProcess("Frozen", uid, pid)
+ || !isProfileValidForProcess("Unfrozen", uid, pid)) {
+ ALOGE("Missing freezer profiles");
+ return false;
+ }
+
+ return true;
+}
+
+static const JNINativeMethod sMethods[] = {
+ {"nativeIsFreezerSupported", "()Z", (void*) isFreezerSupported },
+ {"nativeFreezeBinder", "(IZI)I", (void*) freezeBinder },
+ {"nativeGetBinderFreezeInfo", "(I)I", (void*) getBinderFreezeInfo },
+};
+
+} // end of anonymous namespace
+
+int register_android_server_am_Freezer(JNIEnv* env)
+{
+ char const *className = "com/android/server/am/Freezer";
+ return jniRegisterNativeMethods(env, className, sMethods, NELEM(sMethods));
+}
+
+} // end of namespace android
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/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 314ff9d..3c55d18 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,6 +54,7 @@
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
+int register_android_server_am_Freezer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_android_server_utils_AnrTimer(JNIEnv *env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
@@ -118,6 +119,7 @@
register_android_hardware_display_DisplayViewport(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
+ register_android_server_am_Freezer(env);
register_android_server_am_LowMemDetector(env);
register_android_server_utils_AnrTimer(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
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/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f7aeb89..9ed645b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13634,7 +13634,28 @@
setBackwardCompatibleUserRestriction(
caller, admin, key, enabledFromThisOwner, parent);
}
- logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
+ logUserRestrictionCall(key, enabledFromThisOwner, parent, caller, affectedUserId);
+ }
+
+ @Override
+ public void setUserRestrictionForUser(
+ @NonNull String systemEntity, String key, boolean enabled, @UserIdInt int targetUser) {
+ Objects.requireNonNull(systemEntity);
+
+ CallerIdentity caller = getCallerIdentity();
+ if (caller.getUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only system services can call setUserRestrictionForUser"
+ + " on a target user: " + targetUser);
+ }
+ if (VERBOSE_LOG) {
+ Slogf.v(LOG_TAG, "Creating SystemEnforcingAdmin %s for calling package %s",
+ systemEntity, caller.getPackageName());
+ }
+ EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+
+ setLocalUserRestrictionInternal(admin, key, enabled, targetUser);
+
+ logUserRestrictionCall(key, enabled, /* parent= */ false, caller, targetUser);
}
private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) {
@@ -13755,7 +13776,8 @@
setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
- logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
+ logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller,
+ UserHandle.USER_ALL);
}
private void setLocalUserRestrictionInternal(
EnforcingAdmin admin, String key, boolean enabled, int userId) {
@@ -13791,7 +13813,7 @@
}
private void logUserRestrictionCall(
- String key, boolean enabled, boolean parent, CallerIdentity caller) {
+ String key, boolean enabled, boolean parent, CallerIdentity caller, int targetUserId) {
final int eventId = enabled
? DevicePolicyEnums.ADD_USER_RESTRICTION
: DevicePolicyEnums.REMOVE_USER_RESTRICTION;
@@ -13807,8 +13829,9 @@
SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
}
- Slogf.i(LOG_TAG, "Changing user restriction %s to: %b caller: %s",
- key, enabled, caller.toString());
+ Slogf.i(LOG_TAG, "Changing user restriction %s on %s to: %b caller: %s",
+ key, (targetUserId == UserHandle.USER_ALL ? "all users" : ("user " + targetUserId)),
+ enabled, caller.toString());
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 02590f9..e65e513 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -21,7 +21,6 @@
import android.app.admin.Authority;
import android.app.admin.DeviceAdminAuthority;
import android.app.admin.DpcAuthority;
-import android.app.admin.PackagePermissionPolicyKey;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
import android.content.ComponentName;
@@ -57,23 +56,29 @@
static final String TAG = "EnforcingAdmin";
static final String ROLE_AUTHORITY_PREFIX = "role:";
+ static final String SYSTEM_AUTHORITY_PREFIX = "system:";
static final String DPC_AUTHORITY = "enterprise";
static final String DEVICE_ADMIN_AUTHORITY = "device_admin";
static final String DEFAULT_AUTHORITY = "default";
private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_SYSTEM_ENTITY = "system-entity";
private static final String ATTR_CLASS_NAME = "class-name";
private static final String ATTR_AUTHORITIES = "authorities";
private static final String ATTR_AUTHORITIES_SEPARATOR = ";";
private static final String ATTR_USER_ID = "user-id";
private static final String ATTR_IS_ROLE = "is-role";
+ private static final String ATTR_IS_SYSTEM = "is-system";
private final String mPackageName;
+ // Name of the system entity. Only used when mIsSystemAuthority is true.
+ private final String mSystemEntity;
// This is needed for DPCs and active admins
private final ComponentName mComponentName;
private Set<String> mAuthorities;
private final int mUserId;
private final boolean mIsRoleAuthority;
+ private final boolean mIsSystemAuthority;
private final ActiveAdmin mActiveAdmin;
static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
@@ -106,6 +111,11 @@
userId, activeAdmin);
}
+ static EnforcingAdmin createSystemEnforcingAdmin(@NonNull String systemEntity) {
+ Objects.requireNonNull(systemEntity);
+ return new EnforcingAdmin(systemEntity);
+ }
+
static EnforcingAdmin createEnforcingAdmin(android.app.admin.EnforcingAdmin admin) {
Objects.requireNonNull(admin);
Authority authority = admin.getAuthority();
@@ -127,6 +137,7 @@
/* activeAdmin = */ null,
/* isRoleAuthority = */ true);
}
+ // TODO(b/324899199): Consider supporting android.app.admin.SystemAuthority.
return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
Set.of(), admin.getUserHandle().getIdentifier(),
/* activeAdmin = */ null);
@@ -159,9 +170,11 @@
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
- // Role authorities should not be using this constructor
+ // Role/System authorities should not be using this constructor
mIsRoleAuthority = false;
+ mIsSystemAuthority = false;
mPackageName = packageName;
+ mSystemEntity = null;
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
@@ -173,7 +186,9 @@
// Only role authorities use this constructor.
mIsRoleAuthority = true;
+ mIsSystemAuthority = false;
mPackageName = packageName;
+ mSystemEntity = null;
mUserId = userId;
mComponentName = null;
// authorities will be loaded when needed
@@ -181,6 +196,21 @@
mActiveAdmin = activeAdmin;
}
+ /** Constructor for System authorities. */
+ private EnforcingAdmin(@NonNull String systemEntity) {
+ Objects.requireNonNull(systemEntity);
+
+ // Only system authorities use this constructor.
+ mIsSystemAuthority = true;
+ mIsRoleAuthority = false;
+ mPackageName = null;
+ mSystemEntity = systemEntity;
+ mUserId = UserHandle.USER_SYSTEM;
+ mComponentName = null;
+ mAuthorities = getSystemAuthority(systemEntity);
+ mActiveAdmin = null;
+ }
+
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
@@ -188,7 +218,9 @@
Objects.requireNonNull(authorities);
mIsRoleAuthority = isRoleAuthority;
+ mIsSystemAuthority = false;
mPackageName = packageName;
+ mSystemEntity = null;
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
@@ -204,6 +236,18 @@
return authorities.isEmpty() ? Set.of(DEFAULT_AUTHORITY) : authorities;
}
+ /**
+ * Returns a set of authorities for system authority.
+ *
+ * <p>Note that a system authority enforcing admin has only one authority that has the package
+ * name of the calling system service. Therefore, the returned set always contains one element.
+ */
+ private static Set<String> getSystemAuthority(String systemEntity) {
+ Set<String> authorities = new HashSet<>();
+ authorities.add(SYSTEM_AUTHORITY_PREFIX + systemEntity);
+ return authorities;
+ }
+
// TODO(b/259042794): move this logic to RoleManagerLocal
private static Set<String> getRoles(String packageName, int userId) {
RoleManagerLocal roleManagerLocal = LocalManagerRegistry.getManager(
@@ -264,6 +308,7 @@
} else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
} else {
+ // For now, System Authority returns UNKNOWN_AUTHORITY.
authority = UnknownAuthority.UNKNOWN_AUTHORITY;
}
return new android.app.admin.EnforcingAdmin(
@@ -291,8 +336,10 @@
if (o == null || getClass() != o.getClass()) return false;
EnforcingAdmin other = (EnforcingAdmin) o;
return Objects.equals(mPackageName, other.mPackageName)
+ && Objects.equals(mSystemEntity, other.mSystemEntity)
&& Objects.equals(mComponentName, other.mComponentName)
&& Objects.equals(mIsRoleAuthority, other.mIsRoleAuthority)
+ && (mIsSystemAuthority == other.mIsSystemAuthority)
&& hasMatchingAuthorities(this, other);
}
@@ -307,6 +354,8 @@
public int hashCode() {
if (mIsRoleAuthority) {
return Objects.hash(mPackageName, mUserId);
+ } else if (mIsSystemAuthority) {
+ return Objects.hash(mSystemEntity);
} else {
return Objects.hash(
mComponentName == null ? mPackageName : mComponentName,
@@ -318,8 +367,12 @@
void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
+ serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_SYSTEM, mIsSystemAuthority);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
- if (!mIsRoleAuthority) {
+ if (mIsSystemAuthority) {
+ serializer.attribute(/* namespace= */ null, ATTR_SYSTEM_ENTITY, mSystemEntity);
+ }
+ if (!mIsRoleAuthority && !mIsSystemAuthority) {
if (mComponentName != null) {
serializer.attribute(
/* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
@@ -336,7 +389,10 @@
static EnforcingAdmin readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException {
String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+ String systemEntity = parser.getAttributeValue(/* namespace= */ null, ATTR_SYSTEM_ENTITY);
boolean isRoleAuthority = parser.getAttributeBoolean(/* namespace= */ null, ATTR_IS_ROLE);
+ boolean isSystemAuthority = parser.getAttributeBoolean(
+ /* namespace= */ null, ATTR_IS_SYSTEM, /* defaultValue= */ false);
String authoritiesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_AUTHORITIES);
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
@@ -348,6 +404,13 @@
}
// TODO(b/281697976): load active admin
return new EnforcingAdmin(packageName, userId, null);
+ } else if (isSystemAuthority) {
+ if (systemEntity == null) {
+ Slogf.wtf(TAG, "Error parsing EnforcingAdmin with SystemAuthority, "
+ + "systemEntity is null.");
+ return null;
+ }
+ return new EnforcingAdmin(systemEntity);
} else {
if (packageName == null || authoritiesStr == null) {
Slogf.wtf(TAG, "Error parsing EnforcingAdmin, packageName is "
@@ -381,6 +444,10 @@
sb.append(mUserId);
sb.append(", mIsRoleAuthority= ");
sb.append(mIsRoleAuthority);
+ sb.append(", mIsSystemAuthority= ");
+ sb.append(mIsSystemAuthority);
+ sb.append(", mSystemEntity = ");
+ sb.append(mSystemEntity);
sb.append(" }");
return sb.toString();
}
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/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index c582a46..dd049303 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -663,7 +663,7 @@
}
}
if (DEBUG) {
- Slogf.d(TAG, "Adding audit %d events to % already present in the buffer",
+ Slogf.d(TAG, "Adding audit %d events to %d already present in the buffer",
events.size(), mAuditLogEventBuffer.size());
}
mAuditLogEventBuffer.addAll(events);
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/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index dd3b33e..8ce2422 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -23,7 +23,6 @@
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
@@ -41,6 +40,7 @@
import static org.mockito.ArgumentMatchers.notNull;
+import android.annotation.UserIdInt;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,7 +50,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.wm.ImeTargetChangeListener;
import com.android.server.wm.WindowManagerInternal;
import org.junit.Before;
@@ -68,7 +67,6 @@
@RunWith(AndroidJUnit4.class)
public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTestBase {
private ImeVisibilityStateComputer mComputer;
- private ImeTargetChangeListener mListener;
private int mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
@Before
@@ -84,12 +82,14 @@
public ImeDisplayValidator getImeValidator() {
return displayId -> mImeDisplayPolicy;
}
+
+ @UserIdInt
+ @Override
+ public int getUserId() {
+ return mUserId;
+ }
};
- ArgumentCaptor<ImeTargetChangeListener> captor = ArgumentCaptor.forClass(
- ImeTargetChangeListener.class);
- verify(mMockWindowManagerInternal).setInputMethodTargetChangeListener(captor.capture());
mComputer = new ImeVisibilityStateComputer(mInputMethodManagerService, injector);
- mListener = captor.getValue();
}
@Test
@@ -298,28 +298,32 @@
@Test
public void testOnApplyImeVisibilityFromComputer() {
synchronized (ImfLock.class) {
- final IBinder testImeTargetOverlay = new Binder();
final IBinder testImeInputTarget = new Binder();
// Simulate a test IME input target was visible.
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
+ mComputer.onImeInputTargetVisibilityChanged(testImeInputTarget,
+ true /* visibleAndNotRemoved */);
// Simulate a test IME layering target overlay fully occluded the IME input target.
- mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
- TYPE_APPLICATION_OVERLAY, true, false);
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
+ mComputer.setHasVisibleImeLayeringOverlay(true /* visibleAndNotRemoved */);
+ mComputer.onImeInputTargetVisibilityChanged(testImeInputTarget,
+ false /* visibleAndNotRemoved */);
final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
ImeVisibilityResult.class);
+ final ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked(
- targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture());
+ targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture(),
+ userIdCaptor.capture());
final IBinder imeInputTarget = targetCaptor.getValue();
final ImeVisibilityResult result = resultCaptor.getValue();
+ final int userId = userIdCaptor.getValue();
// Verify the computer will callback hiding IME state to IMMS.
assertThat(imeInputTarget).isEqualTo(testImeInputTarget);
assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT);
assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE);
+ assertThat(userId).isEqualTo(mUserId);
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
index 2ea2e22..a86d61bb 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java
@@ -122,8 +122,8 @@
private List<InputMethodInfo> filterInputMethodServices(List<ResolveInfo> resolveInfoList,
List<String> enabledComponents) {
- final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices(
- AdditionalSubtypeMap.EMPTY_MAP, enabledComponents, mContext, resolveInfoList);
+ final var methodMap = InputMethodManagerService.filterInputMethodServices(
+ enabledComponents, mContext, resolveInfoList);
return methodMap.values();
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 461697c..3af5db9 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -53,6 +53,7 @@
import android.util.ArraySet;
import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
import android.window.ImeOnBackInvokedDispatcher;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -260,6 +261,11 @@
unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
+ synchronized (ImfLock.class) {
+ doReturn(true).when(mInputMethodManagerService).setImeVisibilityOnFocusedWindowClient(
+ anyBoolean(), any(UserData.class), any(ImeTracker.Token.class));
+ }
+
// Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
// InputMethodManagerService, which is closer to the real situation.
InputMethodManagerService.Lifecycle lifecycle =
@@ -269,15 +275,21 @@
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
lifecycle.onStart();
+ final var userData = mInputMethodManagerService.getUserData(mUserId);
+
// Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
// TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
AdditionalSubtypeMapRepository.initializeIfNecessary(mUserId);
- final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
- mUserId, AdditionalSubtypeMapRepository.get(mUserId), DirectBootAwareness.AUTO);
+ final var rawMethodMap = InputMethodManagerService.queryRawInputMethodServiceMap(mContext,
+ mUserId);
+ userData.mRawInputMethodMap.set(rawMethodMap);
+ final var settings = InputMethodSettings.create(rawMethodMap.toInputMethodMap(
+ AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO, true /* userUnlocked */),
+ mUserId);
InputMethodSettingsRepository.put(mUserId, settings);
// Emulate that the user initialization is done.
- mInputMethodManagerService.getUserData(mUserId).mBackgroundLoadLatch.countDown();
+ userData.mBackgroundLoadLatch.countDown();
// After this boot phase, services can broadcast Intents.
lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
@@ -341,6 +353,14 @@
anyInt() /* flags */, any() /* resultReceiver */);
}
+ protected void verifySetImeVisibility(boolean setVisible, boolean invoked) {
+ synchronized (ImfLock.class) {
+ verify(mInputMethodManagerService,
+ times(invoked ? 1 : 0)).setImeVisibilityOnFocusedWindowClient(eq(setVisible),
+ any(UserData.class), any(ImeTracker.Token.class));
+ }
+ }
+
protected void createSessionForClient(IInputMethodClient client) {
synchronized (ImfLock.class) {
ClientState cs = mInputMethodManagerService.getClientStateLocked(client);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index c5b5668..4d956b2 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -125,27 +125,52 @@
case SOFT_INPUT_STATE_UNSPECIFIED:
boolean showSoftInput =
(mSoftInputAdjustment == SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen;
- verifyShowSoftInput(
- showSoftInput /* setVisible */, showSoftInput /* showSoftInput */);
- // Soft input was hidden by default, so it doesn't need to call
- // {@code IMS#hideSoftInput()}.
- verifyHideSoftInput(!showSoftInput /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, showSoftInput /* invoked */);
+ // A hide can only be triggered if there is no editorFocused, which this test
+ // always sets.
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(showSoftInput /* setVisible */,
+ showSoftInput /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(!showSoftInput /* setNotVisible */,
+ false /* hideSoftInput */);
+ }
break;
case SOFT_INPUT_STATE_VISIBLE:
case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
- verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
- verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, true /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ }
break;
- case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
- verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
- verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ case SOFT_INPUT_STATE_UNCHANGED:
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ }
break;
case SOFT_INPUT_STATE_HIDDEN:
case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
- // Soft input was hidden by default, so it doesn't need to call
- // {@code IMS#hideSoftInput()}.
- verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+ // In this case, we don't have to manipulate the requested visible types of
+ // the WindowState, as they're already in the correct state
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+ }
break;
default:
throw new IllegalStateException(
@@ -167,26 +192,52 @@
case SOFT_INPUT_STATE_UNSPECIFIED:
boolean hideSoftInput =
(mSoftInputAdjustment != SOFT_INPUT_ADJUST_RESIZE) && !mIsLargeScreen;
- verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
- // Soft input was hidden by default, so it doesn't need to call
- // {@code IMS#hideSoftInput()}.
- verifyHideSoftInput(hideSoftInput /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // A show can only be triggered in forward navigation
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ // A hide can only be triggered if there is no editorFocused, which this test
+ // always sets.
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(hideSoftInput /* setNotVisible */,
+ false /* hideSoftInput */);
+ }
break;
case SOFT_INPUT_STATE_VISIBLE:
case SOFT_INPUT_STATE_HIDDEN:
case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
- verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
- verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ }
break;
case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
- verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
- verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, true /* invoked */);
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
+ verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+ }
break;
case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
- // Soft input was hidden by default, so it doesn't need to call
- // {@code IMS#hideSoftInput()}.
- verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ verifySetImeVisibility(true /* setVisible */, false /* invoked */);
+ // In this case, we don't have to manipulate the requested visible types of
+ // the WindowState, as they're already in the correct state
+ verifySetImeVisibility(false /* setVisible */, false /* invoked */);
+ } else {
+ verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+ // Soft input was hidden by default, so it doesn't need to call
+ // {@code IMS#hideSoftInput()}.
+ verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+ }
break;
default:
throw new IllegalStateException(
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/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index d59f28b..b984624 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -19,6 +19,11 @@
import static com.google.common.truth.Truth.assertThat;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
import org.junit.Before;
@@ -43,20 +48,43 @@
@Mock
private InputMethodManagerService mMockInputMethodManagerService;
+ @Mock
+ private WindowManagerInternal mMockWindowManagerInternal;
+
+ @NonNull
private IntFunction<InputMethodBindingController> mBindingControllerFactory;
+ @NonNull
+ private IntFunction<ImeVisibilityStateComputer> mVisibilityStateComputerFactory;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
SecureSettingsWrapper.startTestMode();
- mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
+ mBindingControllerFactory = userId ->
+ new InputMethodBindingController(userId, mMockInputMethodManagerService);
- @Override
- public InputMethodBindingController apply(int userId) {
- return new InputMethodBindingController(userId, mMockInputMethodManagerService);
- }
- };
+ mVisibilityStateComputerFactory = userId -> new ImeVisibilityStateComputer(
+ mMockInputMethodManagerService,
+ new ImeVisibilityStateComputer.Injector() {
+ @NonNull
+ @Override
+ public WindowManagerInternal getWmService() {
+ return mMockWindowManagerInternal;
+ }
+
+ @NonNull
+ @Override
+ public InputMethodManagerService.ImeDisplayValidator getImeValidator() {
+ return displayId -> WindowManager.DISPLAY_IME_POLICY_LOCAL;
+ }
+
+ @Override
+ public int getUserId() {
+ return userId;
+ }
+ });
}
@After
@@ -69,7 +97,8 @@
public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
// Create UserDataRepository
final var repository = new UserDataRepository(
- userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
+ userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService),
+ mVisibilityStateComputerFactory);
// Add one UserData ...
final var userData = repository.getOrCreate(ANY_USER_ID);
@@ -85,7 +114,8 @@
@Test
public void testGetOrCreate() {
- final var repository = new UserDataRepository(mBindingControllerFactory);
+ final var repository = new UserDataRepository(mBindingControllerFactory,
+ mVisibilityStateComputerFactory);
final var userData = repository.getOrCreate(ANY_USER_ID);
assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
@@ -96,6 +126,7 @@
// Assert UserDataRepository called the InputMethodBindingController creator function.
assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID);
+ assertThat(allUserData.get(0).mVisibilityStateComputer.getUserId()).isEqualTo(ANY_USER_ID);
}
private List<UserData> collectUserData(UserDataRepository repository) {
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/mockingservicestests/jni/Android.bp b/services/tests/mockingservicestests/jni/Android.bp
index 1eb9888..00543a8 100644
--- a/services/tests/mockingservicestests/jni/Android.bp
+++ b/services/tests/mockingservicestests/jni/Android.bp
@@ -21,6 +21,7 @@
srcs: [
":lib_cachedAppOptimizer_native",
+ ":lib_freezer_native",
":lib_gameManagerService_native",
":lib_oomConnection_native",
"onload.cpp",
diff --git a/services/tests/mockingservicestests/jni/onload.cpp b/services/tests/mockingservicestests/jni/onload.cpp
index fb91051..cb246d1 100644
--- a/services/tests/mockingservicestests/jni/onload.cpp
+++ b/services/tests/mockingservicestests/jni/onload.cpp
@@ -25,6 +25,7 @@
namespace android {
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
+int register_android_server_am_Freezer(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
};
@@ -42,8 +43,8 @@
}
ALOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_am_CachedAppOptimizer(env);
+ register_android_server_am_Freezer(env);
register_android_server_app_GameManagerService(env);
register_android_server_am_OomConnection(env);
return JNI_VERSION_1_4;
}
-
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index cb15d6f..b980ca0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -132,6 +132,7 @@
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
@@ -149,6 +150,7 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
@@ -176,6 +178,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -250,6 +253,8 @@
@Mock
private ActivityManagerInternal mActivityManagerInternal;
@Mock
+ private UserManagerInternal mUserManagerInternal;
+ @Mock
private ActivityManager mActivityManager;
@Mock
private PackageManagerInternal mPackageManagerInternal;
@@ -447,6 +452,8 @@
() -> LocalServices.getService(PermissionManagerServiceInternal.class));
doReturn(mActivityManagerInternal).when(
() -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mUserManagerInternal).when(
+ () -> LocalServices.getService(UserManagerInternal.class));
doReturn(mPackageManagerInternal).when(
() -> LocalServices.getService(PackageManagerInternal.class));
doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class));
@@ -1252,6 +1259,26 @@
}
@Test
+ public void wakeupShouldBeScheduledForFullUsers_skipsGuestSystemAndProfiles() {
+ final int systemUserId = 0;
+ final int fullUserId = 10;
+ final int privateProfileId = 12;
+ final int guestUserId = 13;
+ when(mUserManagerInternal.getUserInfo(fullUserId)).thenReturn(new UserInfo(fullUserId,
+ "TestUser2", UserInfo.FLAG_FULL));
+ when(mUserManagerInternal.getUserInfo(privateProfileId)).thenReturn(new UserInfo(
+ privateProfileId, "TestUser3", UserInfo.FLAG_PROFILE));
+ when(mUserManagerInternal.getUserInfo(guestUserId)).thenReturn(new UserInfo(
+ guestUserId, "TestUserGuest", null, 0, UserManager.USER_TYPE_FULL_GUEST));
+ when(mUserManagerInternal.getUserInfo(systemUserId)).thenReturn(new UserInfo(
+ systemUserId, "TestUserSystem", null, 0, UserManager.USER_TYPE_FULL_SYSTEM));
+ assertTrue(mService.shouldAddWakeupForUser(fullUserId));
+ assertFalse(mService.shouldAddWakeupForUser(systemUserId));
+ assertFalse(mService.shouldAddWakeupForUser(privateProfileId));
+ assertFalse(mService.shouldAddWakeupForUser(guestUserId));
+ }
+
+ @Test
public void sendsTimeTickOnInteractive() {
final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
// Stubbing so the handler doesn't actually run the runnable.
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
index 5bd919f..72883e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/UserWakeupStoreTest.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.testng.AssertJUnit.assertFalse;
import android.os.Environment;
import android.os.FileUtils;
@@ -52,7 +51,6 @@
private static final int USER_ID_1 = 10;
private static final int USER_ID_2 = 11;
private static final int USER_ID_3 = 12;
- private static final int USER_ID_SYSTEM = 0;
private static final long TEST_TIMESTAMP = 150_000;
private static final File TEST_SYSTEM_DIR = new File(InstrumentationRegistry
.getInstrumentation().getContext().getDataDir(), "alarmsTestDir");
@@ -112,14 +110,6 @@
}
@Test
- public void testAddWakeupForSystemUser_shouldDoNothing() {
- mUserWakeupStore.addUserWakeup(USER_ID_SYSTEM, TEST_TIMESTAMP - 19_000);
- assertEquals(0, mUserWakeupStore.getUserIdsToWakeup(TEST_TIMESTAMP).length);
- final File file = new File(ROOT_DIR , "usersWithAlarmClocks.xml");
- assertFalse(file.exists());
- }
-
- @Test
public void testAddMultipleWakeupsForUser_ensureOnlyLastWakeupRemains() {
final long finalAlarmTime = TEST_TIMESTAMP - 13_000;
mUserWakeupStore.addUserWakeup(USER_ID_1, TEST_TIMESTAMP - 29_000);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 03439e55..d203de5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -21,10 +21,21 @@
import static com.android.server.am.ActivityManagerService.Injector;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.FrozenProcessListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -33,12 +44,14 @@
import android.os.HandlerThread;
import android.os.MessageQueue;
import android.os.Process;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.android.server.LocalServices;
@@ -55,9 +68,11 @@
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -79,12 +94,17 @@
private CountDownLatch mCountDown;
private ActivityManagerService mAms;
private Context mContext;
+ private TestFreezer mFreezer;
+ private CountDownLatch mFreezeCounter;
private TestInjector mInjector;
private TestProcessDependencies mProcessDependencies;
@Mock
private PackageManagerInternal mPackageManagerInt;
+ // Control whether the freezer mock reports that freezing is enabled or not.
+ private boolean mUseFreezer;
+
@Rule
public final ApplicationExitInfoTest.ServiceThreadRule
mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
@@ -103,9 +123,12 @@
true /* allowIo */);
mThread.start();
mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ mUseFreezer = false;
+ mFreezer = new TestFreezer();
+
mInjector = new TestInjector(mContext);
- mAms = new ActivityManagerService(
- new TestInjector(mContext), mServiceThreadRule.getThread());
+ mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
mProcessDependencies = new TestProcessDependencies();
mCachedAppOptimizerUnderTest = new CachedAppOptimizer(mAms,
@@ -126,6 +149,7 @@
mHandlerThread.quit();
mThread.quit();
mCountDown = null;
+ mFreezeCounter = null;
}
private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName,
@@ -179,7 +203,7 @@
assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
.containsExactlyElementsIn(expected);
- Assume.assumeTrue(mCachedAppOptimizerUnderTest.isFreezerSupported());
+ Assume.assumeTrue(mAms.isAppFreezerSupported());
assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isEqualTo(
CachedAppOptimizer.DEFAULT_USE_FREEZER);
}
@@ -265,8 +289,8 @@
CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
- Assume.assumeTrue(CachedAppOptimizer.isFreezerSupported());
- if (CachedAppOptimizer.isFreezerSupported()) {
+ Assume.assumeTrue(mAms.isAppFreezerSupported());
+ if (mAms.isAppFreezerSupported()) {
if (CachedAppOptimizer.DEFAULT_USE_FREEZER) {
assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isFalse();
} else {
@@ -300,7 +324,7 @@
@Test
public void useFreeze_doesNotListenToDeviceConfigChanges() throws InterruptedException {
- Assume.assumeTrue(CachedAppOptimizer.isFreezerSupported());
+ Assume.assumeTrue(mAms.isAppFreezerSupported());
assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isFalse();
@@ -353,7 +377,7 @@
@Test
public void useFreeze_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- Assume.assumeTrue(CachedAppOptimizer.isFreezerSupported());
+ Assume.assumeTrue(mAms.isAppFreezerSupported());
assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isFalse();
// When we push an invalid flag value...
@@ -982,6 +1006,87 @@
}
}
+ @Test
+ public void testFreezerDelegator() throws Exception {
+ mUseFreezer = true;
+ mProcessDependencies.setRss(new long[] {
+ 0 /*total_rss*/,
+ 0 /*file*/,
+ 0 /*anon*/,
+ 0 /*swap*/,
+ 0 /*shmem*/
+ });
+
+ // Force the system to use the freezer
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ CachedAppOptimizer.KEY_USE_FREEZER, "true", false);
+ mCachedAppOptimizerUnderTest.init();
+ initActivityManagerService();
+
+ assertTrue(mAms.isAppFreezerSupported());
+ assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isTrue();
+
+ int pid = 10000;
+ int uid = 2;
+ int pkgUid = 3;
+ ProcessRecord app = makeProcessRecord(pid, uid, pkgUid, "p1", "app1");
+
+ mFreezeCounter = new CountDownLatch(1);
+ mCachedAppOptimizerUnderTest.forceFreezeForTest(app, true);
+ assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
+
+ mFreezeCounter = new CountDownLatch(1);
+ mCachedAppOptimizerUnderTest.forceFreezeForTest(app, false);
+ assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testFrozenNotifier() throws Exception {
+ mUseFreezer = true;
+ mProcessDependencies.setRss(new long[] {
+ 0 /*total_rss*/,
+ 0 /*file*/,
+ 0 /*anon*/,
+ 0 /*swap*/,
+ 0 /*shmem*/
+ });
+
+ // Force the system to use the freezer
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ CachedAppOptimizer.KEY_USE_FREEZER, "true", false);
+ mCachedAppOptimizerUnderTest.init();
+ initActivityManagerService();
+
+ assertTrue(mAms.isAppFreezerSupported());
+ assertThat(mCachedAppOptimizerUnderTest.useFreezer()).isTrue();
+
+ int pid = 10000;
+ int uid = 2;
+ int pkgUid = 3;
+ ProcessRecord app = makeProcessRecord(pid, uid, pkgUid, "p1", "app1");
+ assertNotNull(app.mOptRecord);
+
+ FrozenProcessListener listener = new FrozenProcessListener() {
+ @Override
+ public void onProcessFrozen(int pid) {
+ mFreezeCounter.countDown();
+ }
+ @Override
+ public void onProcessUnfrozen(int pid) {
+ mFreezeCounter.countDown();
+ }
+ };
+ mCachedAppOptimizerUnderTest.addFrozenProcessListener(app, directExecutor(), listener);
+
+ mFreezeCounter = new CountDownLatch(2);
+ mCachedAppOptimizerUnderTest.forceFreezeForTest(app, true);
+ assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
+
+ mFreezeCounter = new CountDownLatch(2);
+ mCachedAppOptimizerUnderTest.forceFreezeForTest(app, false);
+ assertTrue(mFreezeCounter.await(5, TimeUnit.SECONDS));
+ }
+
private void setFlag(String key, String value, boolean defaultValue) throws Exception {
mCountDown = new CountDownLatch(1);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue);
@@ -1042,6 +1147,11 @@
public Handler getUiHandler(ActivityManagerService service) {
return mHandler;
}
+
+ @Override
+ public Freezer getFreezer() {
+ return mFreezer;
+ }
}
// Test implementation for ProcessDependencies.
@@ -1069,4 +1179,27 @@
mRssAfterCompaction = newValues;
}
}
+
+ // Intercept Freezer calls.
+ private class TestFreezer extends Freezer {
+ @Override
+ public void setProcessFrozen(int pid, int uid, boolean frozen) {
+ mFreezeCounter.countDown();
+ }
+
+ @Override
+ public int freezeBinder(int pid, boolean freeze, int timeoutMs) {
+ return 0;
+ }
+
+ @Override
+ public int getBinderFreezeInfo(int pid) {
+ return 0;
+ }
+
+ @Override
+ public boolean isFreezerSupported() {
+ return mUseFreezer;
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 2cbc226..4fac647 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -1,3 +1,4 @@
include /services/core/java/com/android/server/am/OWNERS
per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
+per-file CachedAppOptimizerTest.java = file:/PERFORMANCE_OWNERS
diff --git a/services/tests/servicestests/jni/Android.bp b/services/tests/servicestests/jni/Android.bp
index c30e4eb..0a31037 100644
--- a/services/tests/servicestests/jni/Android.bp
+++ b/services/tests/servicestests/jni/Android.bp
@@ -21,6 +21,7 @@
srcs: [
":lib_cachedAppOptimizer_native",
+ ":lib_freezer_native",
":lib_gameManagerService_native",
":lib_oomConnection_native",
":lib_anrTimer_native",
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/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 7b71f85..1426d5d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -48,6 +48,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -67,6 +68,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
@@ -118,6 +120,7 @@
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
+ final Resources mMockResources = mock(Resources.class);
final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
private final MagnificationAnimationCallback mAnimationCallback = mock(
@@ -162,6 +165,7 @@
mResolver = new MockContentResolver();
mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mMockContext.getContentResolver()).thenReturn(mResolver);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(mResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
CURRENT_USER_ID);
@@ -928,7 +932,8 @@
/* displayId= */ i,
/* isMagnifierActivated= */ true,
/* isAlwaysOnEnabled= */ false,
- /* expectedActivated= */ false);
+ /* expectedActivated= */ false,
+ /* expectedMagnified= */ false);
resetMockWindowManager();
}
}
@@ -940,7 +945,24 @@
/* displayId= */ i,
/* isMagnifierActivated= */ true,
/* isAlwaysOnEnabled= */ true,
- /* expectedActivated= */ true);
+ /* expectedActivated= */ true,
+ /* expectedMagnified= */ false);
+ resetMockWindowManager();
+ }
+ }
+
+ @Test
+ public void testUserContextChange_magnifierActivatedAndKeepMagnifiedEnabled_stayActivated() {
+ when(mMockResources.getBoolean(
+ R.bool.config_magnification_keep_zoom_level_when_context_changed))
+ .thenReturn(true);
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ contextChange_expectedValues(
+ /* displayId= */ i,
+ /* isMagnifierActivated= */ true,
+ /* isAlwaysOnEnabled= */ true,
+ /* expectedActivated= */ true,
+ /* expectedMagnified= */ true);
resetMockWindowManager();
}
}
@@ -952,7 +974,8 @@
/* displayId= */ i,
/* isMagnifierActivated= */ false,
/* isAlwaysOnEnabled= */ false,
- /* expectedActivated= */ false);
+ /* expectedActivated= */ false,
+ /* expectedMagnified= */ false);
resetMockWindowManager();
}
}
@@ -964,14 +987,15 @@
/* displayId= */ i,
/* isMagnifierActivated= */ false,
/* isAlwaysOnEnabled= */ true,
- /* expectedActivated= */ false);
+ /* expectedActivated= */ false,
+ /* expectedMagnified= */ false);
resetMockWindowManager();
}
}
private void contextChange_expectedValues(
int displayId, boolean isMagnifierActivated, boolean isAlwaysOnEnabled,
- boolean expectedActivated) {
+ boolean expectedActivated, boolean expectedMagnified) {
mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(isAlwaysOnEnabled);
register(displayId);
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
@@ -982,7 +1006,7 @@
callbacks.onUserContextChanged();
mMessageCapturingHandler.sendAllMessages();
checkActivatedAndMagnifying(
- /* activated= */ expectedActivated, /* magnifying= */ false, displayId);
+ /* activated= */ expectedActivated, expectedMagnified, displayId);
if (expectedActivated) {
verify(mMockThumbnail, times(2)).setThumbnailBounds(
@@ -1526,8 +1550,8 @@
private void checkActivatedAndMagnifying(boolean activated, boolean magnifying, int displayId) {
final boolean isActivated = mFullScreenMagnificationController.isActivated(displayId);
final boolean isMagnifying = mFullScreenMagnificationController.getScale(displayId) > 1.0f;
- assertTrue(isActivated == activated);
- assertTrue(isMagnifying == magnifying);
+ assertEquals(isActivated, activated);
+ assertEquals(isMagnifying, magnifying);
}
private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 60bcecc..957ee06 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility.magnification;
import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
@@ -27,6 +28,8 @@
import static com.android.server.testutils.TestUtils.strictMock;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -300,7 +303,8 @@
mMockFullScreenMagnificationVibrationHelper,
mMockMagnificationLogger,
ViewConfiguration.get(mContext),
- mMockOneFingerPanningSettingsProvider);
+ mMockOneFingerPanningSettingsProvider,
+ new MouseEventHandler(mFullScreenMagnificationController));
// OverscrollHandler is only supported on watches.
// @See config_enable_a11y_fullscreen_magnification_overscroll_handler
if (isWatch()) {
@@ -1398,6 +1402,302 @@
mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
}
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testMouseMoveEventsDoNotMoveMagnifierViewport() {
+ runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ public void testStylusMoveEventsDoNotMoveMagnifierViewport() {
+ runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
+ runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() {
+ runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testMouseHoverMoveEventsMoveMagnifierViewport() {
+ runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testStylusHoverMoveEventsMoveMagnifierViewport() {
+ runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testMouseDownEventsDoNotMoveMagnifierViewport() {
+ runDownDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testStylusDownEventsDoNotMoveMagnifierViewport() {
+ runDownDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testMouseUpEventsDoNotMoveMagnifierViewport() {
+ runUpDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testStylusUpEventsDoNotMoveMagnifierViewport() {
+ runUpDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+ public void testMouseMoveEventsMoveMagnifierViewport() {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 6.2f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ MotionEvent event = mouseEvent(centerX, centerY, ACTION_HOVER_MOVE);
+ send(event, InputDevice.SOURCE_MOUSE);
+ fastForward(20);
+ event = mouseEvent(centerX, centerY, ACTION_DOWN);
+ send(event, InputDevice.SOURCE_MOUSE);
+ fastForward(20);
+
+ // Mouse drag event does impact magnifier viewport.
+ event = mouseEvent(centerX + 30, centerY + 60, ACTION_MOVE);
+ send(event, InputDevice.SOURCE_MOUSE);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0))
+ .isEqualTo(centerX + 30);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0))
+ .isEqualTo(centerY + 60);
+
+ // The mouse events were not consumed by magnifier.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(3);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+ assertThat(eventCaptor.mEvents.get(2).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_MOVE));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
+ private void runHoverMovesViewportTest(int source) {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+
+ // HOVER_MOVE should change magnifier viewport.
+ MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE);
+ send(event, source);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0))
+ .isEqualTo(centerX + 20);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+ // Make sure mouse events are sent onward and not blocked after moving the viewport.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(1);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(source);
+
+ // Send another hover.
+ event = motionEvent(centerX + 20, centerY + 40, ACTION_HOVER_MOVE);
+ send(event, source);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0))
+ .isEqualTo(centerX + 20);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0))
+ .isEqualTo(centerY + 40);
+
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(2);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(source);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
+ private void runDownDoesNotMoveViewportTest(int source) {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 5.3f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
+ send(event, source);
+ fastForward(20);
+
+ // Down event doesn't impact magnifier viewport.
+ event = motionEvent(centerX + 20, centerY + 40, ACTION_DOWN);
+ send(event, source);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+ // The events were not consumed by magnifier.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(2);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(source);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(source);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
+ private void runUpDoesNotMoveViewportTest(int source) {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 2.7f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ MotionEvent event = motionEvent(centerX, centerY, ACTION_HOVER_MOVE);
+ send(event, source);
+ fastForward(20);
+ event = motionEvent(centerX, centerY, ACTION_DOWN);
+ send(event, source);
+ fastForward(20);
+
+ // Up event should not move the viewport.
+ event = motionEvent(centerX + 30, centerY + 60, ACTION_UP);
+ send(event, source);
+ fastForward(20);
+
+ // The events were not consumed by magnifier.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(3);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(source);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(source);
+ assertThat(eventCaptor.mEvents.get(2).getSource()).isEqualTo(source);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_UP));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
+ private void runMoveEventsDoNotMoveMagnifierViewport(int source) {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 3.8f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
+ centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
+
+ MotionEvent event = motionEvent(centerX, centerY, ACTION_DOWN);
+ send(event, source);
+ fastForward(20);
+
+ // Drag event doesn't impact magnifier viewport.
+ event = stylusEvent(centerX + 18, centerY + 42, ACTION_MOVE);
+ send(event, source);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+ // The events were not consumed by magnifier.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(2);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(source);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(source);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_DOWN));
+ expectedActions.add(Integer.valueOf(ACTION_MOVE));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
+ private void runHoverMoveEventsDoNotMoveMagnifierViewport(int source) {
+ final EventCaptor eventCaptor = new EventCaptor();
+ mMgh.setNext(eventCaptor);
+
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+ float centerY =
+ (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+ float scale = 4.0f; // value is unimportant but unique among tests to increase coverage.
+ mFullScreenMagnificationController.setScaleAndCenter(
+ DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+ centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
+ centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
+
+ // HOVER_MOVE should not change magnifier viewport.
+ MotionEvent event = motionEvent(centerX + 20, centerY, ACTION_HOVER_MOVE);
+ send(event, source);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+ // Make sure events are sent onward and not blocked after moving the viewport.
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(1);
+ assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(source);
+
+ // Send another hover.
+ event = motionEvent(centerX + 20, centerY + 40, ACTION_HOVER_MOVE);
+ send(event, source);
+ fastForward(20);
+
+ assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX);
+ assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+ assertThat(eventCaptor.mEvents.size()).isEqualTo(2);
+ assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(source);
+
+ final List<Integer> expectedActions = new ArrayList();
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ expectedActions.add(Integer.valueOf(ACTION_HOVER_MOVE));
+ assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+ }
+
private void enableOneFingerPanning(boolean enable) {
mMockOneFingerPanningEnabled = enable;
when(mMockOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()).thenReturn(enable);
@@ -1795,8 +2095,14 @@
mMgh.notifyShortcutTriggered();
}
+ /** Sends the MotionEvent from a Touchscreen source */
private void send(MotionEvent event) {
- event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ send(event, InputDevice.SOURCE_TOUCHSCREEN);
+ }
+
+ /** Sends the MotionEvent from the given source type. */
+ private void send(MotionEvent event, int source) {
+ event.setSource(source);
try {
mMgh.onMotionEvent(event, event, /* policyFlags */ 0);
} catch (Throwable t) {
@@ -1810,9 +2116,30 @@
return ev;
}
+ private static MotionEvent fromMouse(MotionEvent ev) {
+ ev.setSource(InputDevice.SOURCE_MOUSE);
+ return ev;
+ }
+
+ private static MotionEvent fromStylus(MotionEvent ev) {
+ ev.setSource(InputDevice.SOURCE_STYLUS);
+ return ev;
+ }
+
+ private MotionEvent motionEvent(float x, float y, int action) {
+ return MotionEvent.obtain(mLastDownTime, mClock.now(), action, x, y, 0);
+ }
+
+ private MotionEvent mouseEvent(float x, float y, int action) {
+ return fromMouse(motionEvent(x, y, action));
+ }
+
+ private MotionEvent stylusEvent(float x, float y, int action) {
+ return fromStylus(motionEvent(x, y, action));
+ }
+
private MotionEvent moveEvent(float x, float y) {
- return fromTouchscreen(
- MotionEvent.obtain(mLastDownTime, mClock.now(), ACTION_MOVE, x, y, 0));
+ return fromTouchscreen(motionEvent(x, y, ACTION_MOVE));
}
private MotionEvent downEvent() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index a34e796..242880c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -178,7 +178,7 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINT_LOE)
+ @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINTS_LOE)
public void invalidBiometricUserState() throws Exception {
mClient = createClient();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index e078238..9317d06 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -42,9 +42,11 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
+import android.os.Binder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
@@ -518,19 +520,6 @@
}
@Test
- public void canActivityBeLaunched_permissionComponent_isBlocked() {
- GenericWindowPolicyController gwpc = createGwpcWithPermissionComponent(BLOCKED_COMPONENT);
- gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
-
- ActivityInfo activityInfo = getActivityInfo(
- BLOCKED_PACKAGE_NAME,
- BLOCKED_PACKAGE_NAME,
- /* displayOnRemoteDevices */ true,
- /* targetDisplayCategory */ null);
- assertActivityIsBlocked(gwpc, activityInfo);
- }
-
- @Test
public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID));
GenericWindowPolicyController gwpc = createGwpc();
@@ -605,13 +594,15 @@
// register interceptor and intercept intent
when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(true);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isFalse();
// unregister interceptor and launch activity
when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
}
@@ -630,13 +621,36 @@
// register interceptor with different filter
when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
verify(mIntentListenerCallback, timeout(TIMEOUT_MILLIS))
.shouldInterceptIntent(any(Intent.class));
}
@Test
+ public void canActivityBeLaunched_resultExpected_noIntentSenderInCallback() {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("testing"));
+
+ GenericWindowPolicyController gwpc = createGwpc();
+ gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
+ ActivityInfo activityInfo = getActivityInfo(
+ NONBLOCKED_APP_PACKAGE_NAME,
+ NONBLOCKED_APP_PACKAGE_NAME,
+ /* displayOnRemoteDevices */ false,
+ /* targetDisplayCategory */ null);
+
+ IntentSender intentSender = new IntentSender(new Binder());
+ assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
+ /* isResultExpected = */ true, /* intentSender= */ () -> intentSender))
+ .isFalse();
+
+ verify(mActivityBlockedCallback, timeout(TIMEOUT_MILLIS))
+ .onActivityBlocked(DISPLAY_ID, activityInfo, /* intentSender= */ null);
+ }
+
+ @Test
public void onTopActivitychanged_null_noCallback() {
GenericWindowPolicyController gwpc = createGwpc();
gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
@@ -672,7 +686,8 @@
verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never())
.onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid);
- verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+ verify(mActivityBlockedCallback, never())
+ .onActivityBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
@Test
@@ -691,7 +706,7 @@
verify(mSecureWindowCallback, timeout(TIMEOUT_MILLIS)).onSecureWindowShown(DISPLAY_ID,
activityInfo.applicationInfo.uid);
verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
- .onActivityBlocked(DISPLAY_ID, activityInfo);
+ .onActivityBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
@Test
@@ -710,7 +725,8 @@
verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never())
.onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid);
- verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+ verify(mActivityBlockedCallback, never())
+ .onActivityBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
}
@Test
@@ -740,7 +756,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ mSecureWindowCallback,
@@ -760,7 +775,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ mSecureWindowCallback,
@@ -781,7 +795,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -802,7 +815,6 @@
/* activityPolicyExemptions= */ Collections.singleton(blockedComponent),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -823,7 +835,6 @@
/* activityPolicyExemptions= */ Collections.singleton(allowedComponent),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -844,7 +855,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -865,7 +875,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent),
- /* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -886,29 +895,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ false,
/* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent),
- /* permissionDialogComponent= */ null,
- /* activityListener= */ mActivityListener,
- /* activityBlockedCallback= */ mActivityBlockedCallback,
- /* secureWindowCallback= */ null,
- /* intentListenerCallback= */ mIntentListenerCallback,
- /* displayCategories= */ new ArraySet<>(),
- /* showTasksInHostDeviceRecents= */ true,
- /* customHomeComponent= */ null);
- }
-
- private GenericWindowPolicyController createGwpcWithPermissionComponent(
- ComponentName permissionComponent) {
- //TODO instert the component
- return new GenericWindowPolicyController(
- 0,
- 0,
- AttributionSource.myAttributionSource(),
- /* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
- /* activityLaunchAllowedByDefault= */ true,
- /* activityPolicyExemptions= */ new ArraySet<>(),
- /* crossTaskNavigationAllowedByDefault= */ false,
- /* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent= */ permissionComponent,
/* activityListener= */ mActivityListener,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
@@ -954,11 +940,12 @@
private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc, int fromDisplay,
boolean isNewTask, int windowingMode, ActivityInfo activityInfo) {
+ IntentSender intentSender = new IntentSender(new Binder());
assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
- isNewTask)).isTrue();
+ isNewTask, /* isResultExpected= */ false, () -> intentSender)).isTrue();
verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
- .onActivityBlocked(fromDisplay, activityInfo);
+ .onActivityBlocked(fromDisplay, activityInfo, intentSender);
verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
}
@@ -970,23 +957,26 @@
private void assertActivityIsBlocked(GenericWindowPolicyController gwpc, int fromDisplay,
boolean isNewTask, int windowingMode, ActivityInfo activityInfo) {
+ IntentSender intentSender = new IntentSender(new Binder());
assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
- isNewTask)).isFalse();
+ isNewTask, /* isResultExpected= */ false, () -> intentSender)).isFalse();
verify(mActivityBlockedCallback, timeout(TIMEOUT_MILLIS))
- .onActivityBlocked(fromDisplay, activityInfo);
+ .onActivityBlocked(fromDisplay, activityInfo, intentSender);
verify(mIntentListenerCallback, after(TIMEOUT_MILLIS).never())
.shouldInterceptIntent(any(Intent.class));
}
private void assertNoActivityLaunched(GenericWindowPolicyController gwpc, int fromDisplay,
ActivityInfo activityInfo) {
+ IntentSender intentSender = new IntentSender(new Binder());
assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, true))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
+ /* isResultExpected= */ false, () -> intentSender))
.isFalse();
verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
- .onActivityBlocked(fromDisplay, activityInfo);
+ .onActivityBlocked(eq(fromDisplay), eq(activityInfo), any());
verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index da8961d..c288212 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -314,7 +314,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
return blockedAppIntent;
}
@@ -1522,7 +1523,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext, never()).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1543,15 +1545,15 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
- public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_startsWhenFlagIsEnabled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_starts() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
@@ -1565,15 +1567,15 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext, never()).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
- public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_startsWhenFlagIsEnabled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_starts() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
DISPLAY_ID_1);
@@ -1587,7 +1589,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1608,7 +1611,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1629,7 +1633,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1650,7 +1655,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1671,7 +1677,8 @@
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false);
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1693,7 +1700,8 @@
/* displayOnRemoteDevices */ true,
/* targetDisplayCategory */ null);
assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/ false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isFalse();
// Verify that BlockedAppStreamingActivity also doesn't launch for mirror displays.
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
@@ -1718,7 +1726,8 @@
/* displayOnRemoteDevices */ true,
/* targetDisplayCategory */ null);
assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/ false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
activityInfo, mAssociationInfo.getDisplayName());
@@ -1755,35 +1764,7 @@
}
@Test
- public void canActivityBeLaunched_permissionDialog_flagDisabled_isBlocked() {
- mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
- VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
- mDeviceImpl.close();
- mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
- doNothing().when(mContext).startActivityAsUser(any(), any(), any());
-
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
- DISPLAY_ID_1);
- ComponentName permissionComponent = getPermissionDialogComponent();
- ActivityInfo activityInfo = getActivityInfo(
- permissionComponent.getPackageName(),
- permissionComponent.getClassName(),
- /* displayOnRemoteDevices */ true,
- /* targetDisplayCategory */ null);
- assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
- .isFalse();
-
- Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
- activityInfo, mAssociationInfo.getDisplayName());
- verify(mContext).startActivityAsUser(argThat(intent ->
- intent.filterEquals(blockedAppIntent)), any(), any());
- }
-
- @Test
- public void canActivityBeLaunched_permissionDialog_flagEnabled_isStreamed() {
- mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS);
+ public void canActivityBeLaunched_permissionDialog_isStreamed() {
VirtualDeviceParams params = new VirtualDeviceParams.Builder().build();
mDeviceImpl.close();
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
@@ -1798,7 +1779,8 @@
/* displayOnRemoteDevices */ true,
/* targetDisplayCategory */ null);
assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
}
@@ -1814,7 +1796,8 @@
/* displayOnRemoteDevices */ true,
/* targetDisplayCategory */ null);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
}
@@ -1845,7 +1828,8 @@
// register interceptor and intercept intent
mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isFalse();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(interceptor).onIntentIntercepted(intentCaptor.capture());
@@ -1857,7 +1841,8 @@
// unregister interceptor and launch activity
mDeviceImpl.unregisterIntentInterceptor(interceptor);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
}
@@ -1888,7 +1873,8 @@
mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
- WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+ WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
+ /* isResultExpected = */ false, /* intentSender= */ null))
.isTrue();
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index b946a43..c3db396 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -85,7 +85,6 @@
/* activityPolicyExemptions= */ new ArraySet<>(),
/* crossTaskNavigationAllowedByDefault= */ true,
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
- /* permissionDialogComponent */ null,
/* activityListener= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
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/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 e06d939..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
//
@@ -2454,6 +2486,50 @@
}
@Test
+ public void testBeepVolume_politeNotif_Avalanche_exemptCategories() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+ // CATEGORY_ALARM is exempted
+ NotificationRecord r = getBeepyNotification();
+ r.getNotification().category = Notification.CATEGORY_ALARM;
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+
+ // CATEGORY_CAR_EMERGENCY is exempted
+ Mockito.reset(mRingtonePlayer);
+ NotificationRecord r2 = getBeepyNotification();
+ r2.getNotification().category = Notification.CATEGORY_CAR_EMERGENCY;
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+
+ // CATEGORY_CAR_WARNING is exempted
+ Mockito.reset(mRingtonePlayer);
+ NotificationRecord r3 = getBeepyNotification();
+ r3.getNotification().category = Notification.CATEGORY_CAR_WARNING;
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, r3.getLastAudiblyAlertedMs());
+
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
@@ -2492,6 +2568,146 @@
}
@Test
+ public void testBeepVolume_politeNotif_exemptCategories() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ 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);
+
+ // CATEGORY_ALARM is exempted
+ NotificationRecord r = getBeepyNotification();
+ r.getNotification().category = Notification.CATEGORY_ALARM;
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // CATEGORY_CAR_WARNING is exempted
+ r = getBeepyNotification();
+ r.getNotification().category = Notification.CATEGORY_CAR_WARNING;
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // CATEGORY_CAR_EMERGENCY is exempted
+ r = getBeepyNotification();
+ r.getNotification().category = Notification.CATEGORY_CAR_EMERGENCY;
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(9)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @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 f7340ab..60c4ac7 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -19,12 +19,20 @@
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.Flags.FLAG_MODES_UI;
import static android.app.Flags.modesUi;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.suppressedEffectsToString;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.SOURCE_UNKNOWN;
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.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;
@@ -219,8 +227,8 @@
priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
- suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
- suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
+ suppressedVisualEffects |= SUPPRESSED_EFFECT_LIGHTS;
+ suppressedVisualEffects |= SUPPRESSED_EFFECT_AMBIENT;
Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders,
priorityMessageSenders, suppressedVisualEffects, 0, priorityConversationsSenders);
@@ -256,8 +264,8 @@
priorityCategories |= Policy.PRIORITY_CATEGORY_REMINDERS;
priorityCategories |= Policy.PRIORITY_CATEGORY_EVENTS;
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
- suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS;
- suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT;
+ suppressedVisualEffects |= SUPPRESSED_EFFECT_LIGHTS;
+ suppressedVisualEffects |= SUPPRESSED_EFFECT_AMBIENT;
Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders,
priorityMessageSenders, suppressedVisualEffects,
@@ -277,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());
}
@@ -309,8 +317,8 @@
config.setAllowMessagesFrom(Policy.PRIORITY_SENDERS_STARRED);
config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE);
config.setSuppressedVisualEffects(config.getSuppressedVisualEffects()
- | Policy.SUPPRESSED_EFFECT_BADGE | Policy.SUPPRESSED_EFFECT_LIGHTS
- | Policy.SUPPRESSED_EFFECT_AMBIENT);
+ | Policy.SUPPRESSED_EFFECT_BADGE | SUPPRESSED_EFFECT_LIGHTS
+ | SUPPRESSED_EFFECT_AMBIENT);
}
ZenPolicy actual = config.getZenPolicy();
@@ -357,8 +365,8 @@
config.setAllowConversationsFrom(CONVERSATION_SENDERS_NONE);
config.setAllowPriorityChannels(false);
config.setSuppressedVisualEffects(config.getSuppressedVisualEffects()
- | Policy.SUPPRESSED_EFFECT_BADGE | Policy.SUPPRESSED_EFFECT_LIGHTS
- | Policy.SUPPRESSED_EFFECT_AMBIENT);
+ | Policy.SUPPRESSED_EFFECT_BADGE | SUPPRESSED_EFFECT_LIGHTS
+ | SUPPRESSED_EFFECT_AMBIENT);
}
ZenPolicy actual = config.getZenPolicy();
@@ -529,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();
@@ -644,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();
@@ -985,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()) {
@@ -1063,6 +1123,43 @@
.isEqualTo("name");
}
+ @Test
+ public void toNotificationPolicy_withNewSuppressedEffects_returnsSuppressedEffects() {
+ ZenModeConfig config = getCustomConfig();
+ // From LegacyNotificationManagerTest.testSetNotificationPolicy_preP_setNewFields
+ // When a pre-P app sets SUPPRESSED_EFFECT_NOTIFICATION_LIST, it's converted by NMS into:
+ Policy policy = new Policy(0, 0, 0,
+ SUPPRESSED_EFFECT_FULL_SCREEN_INTENT | SUPPRESSED_EFFECT_LIGHTS
+ | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_AMBIENT);
+
+ config.applyNotificationPolicy(policy);
+ Policy result = config.toNotificationPolicy();
+
+ assertThat(suppressedEffectsOf(result)).isEqualTo(suppressedEffectsOf(policy));
+ }
+
+ @Test
+ public void toNotificationPolicy_withOldAndNewSuppressedEffects_returnsSuppressedEffects() {
+ ZenModeConfig config = getCustomConfig();
+ // From LegacyNotificationManagerTest.testSetNotificationPolicy_preP_setOldNewFields.
+ // When a pre-P app sets SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_STATUS_BAR, it's
+ // converted by NMS into:
+ Policy policy = new Policy(0, 0, 0,
+ SUPPRESSED_EFFECT_SCREEN_ON | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
+ | SUPPRESSED_EFFECT_LIGHTS | SUPPRESSED_EFFECT_PEEK
+ | SUPPRESSED_EFFECT_AMBIENT);
+
+ config.applyNotificationPolicy(policy);
+ Policy result = config.toNotificationPolicy();
+
+ assertThat(suppressedEffectsOf(result)).isEqualTo(suppressedEffectsOf(policy));
+ }
+
+ private static String suppressedEffectsOf(Policy policy) {
+ return suppressedEffectsToString(policy.suppressedVisualEffects) + "("
+ + policy.suppressedVisualEffects + ")";
+ }
+
private ZenModeConfig getMutedRingerConfig() {
ZenModeConfig config = new ZenModeConfig();
@@ -1195,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/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 57587f7..9af0021 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -61,6 +61,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -80,19 +81,6 @@
? Set.of("version", "manualRule", "automaticRules", "deletedRules")
: Set.of("version", "manualRule", "automaticRules");
- // Differences for flagged fields are only generated if the flag is enabled.
- // "Metadata" fields (userModifiedFields, deletionInstant, disabledOrigin) are not compared.
- private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
- android.app.Flags.modesApi()
- ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
- "zenDeviceEffectsUserModifiedFields", "deletionInstant",
- "disabledOrigin")
- : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
- RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
- RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields",
- "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields",
- "deletionInstant", "disabledOrigin");
-
// allowPriorityChannels is flagged by android.app.modes_api
public static final Set<String> ZEN_MODE_CONFIG_FLAGGED_FIELDS =
Set.of("allowPriorityChannels");
@@ -102,8 +90,7 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(
- FLAG_MODES_UI);
+ return FlagsParameterization.progressionOf(FLAG_MODES_API, FLAG_MODES_UI);
}
public ZenModeDiffTest(FlagsParameterization flags) {
@@ -140,7 +127,7 @@
ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
ArrayMap<String, Object> expectedTo = new ArrayMap<>();
List<Field> fieldsForDiff = getFieldsForDiffCheck(
- ZenModeConfig.ZenRule.class, ZEN_RULE_EXEMPT_FIELDS);
+ ZenModeConfig.ZenRule.class, getZenRuleExemptFields());
generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
@@ -158,6 +145,25 @@
}
}
+ private static Set<String> getZenRuleExemptFields() {
+ // "Metadata" fields are never compared.
+ Set<String> exemptFields = new LinkedHashSet<>(
+ Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
+ "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin"));
+ // Flagged fields are only compared if their flag is on.
+ if (!Flags.modesApi()) {
+ exemptFields.addAll(
+ Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
+ RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
+ RuleDiff.FIELD_ZEN_DEVICE_EFFECTS,
+ RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS));
+ }
+ if (!(Flags.modesApi() && Flags.modesUi())) {
+ exemptFields.add(RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS);
+ }
+ return exemptFields;
+ }
+
@Test
public void testConfigDiff_addRemoveSame() {
// Default config, will test add, remove, and no change
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/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index 50041d0..d147325 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -35,6 +35,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Handler;
@@ -89,9 +90,12 @@
testActivityInfo.applicationInfo = new ApplicationInfo();
testActivityInfo.packageName =
testActivityInfo.applicationInfo.packageName = "com.test";
+ ResolveInfo testResolveInfo = new ResolveInfo();
+ testResolveInfo.activityInfo = testActivityInfo;
doReturn(testActivityInfo).when(mPackageManager).getActivityInfo(
eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt());
+ doReturn(testResolveInfo).when(mPackageManager).resolveActivity(anyObject(), anyInt());
doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager)
.getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")),
anyInt());
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/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index 2e85025..9b92ff4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -36,6 +36,7 @@
import android.provider.Settings;
import android.view.Display;
+import org.junit.Before;
import org.junit.Test;
/**
@@ -48,6 +49,13 @@
private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity";
+ @Before
+ public void setup() {
+ super.setup();
+ overrideResource(com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior,
+ LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
+ }
+
/**
* Stem single key should not launch behavior during set up.
*/
@@ -186,6 +194,26 @@
}
@Test
+ public void stemLongKey_appHasOverridePermission_consumedByApp_triggerStatusBarToStartAssist() {
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_LONG_PRESS,
+ LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.setupAssistForLaunch();
+ mPhoneWindowManager.overrideSearchManager(null);
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+ mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true);
+
+ setDispatchedKeyHandler(keyEvent -> true);
+
+ sendKey(KEYCODE_STEM_PRIMARY, /* longPress= */ true);
+
+ mPhoneWindowManager.assertStatusBarStartAssist();
+ }
+
+ @Test
public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent()
throws RemoteException {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
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 220248c..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();
@@ -178,6 +213,10 @@
doReturn(enabled).when(mActivityStack.top()).shouldCreateCompatDisplayInsets();
}
+ void setTopActivityInSizeCompatMode(boolean inScm) {
+ doReturn(inScm).when(mActivityStack.top()).inSizeCompatMode();
+ }
+
void setShouldApplyUserFullscreenOverride(boolean enabled) {
doReturn(enabled).when(mActivityStack.top().mAppCompatController
.getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
@@ -234,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());
}
@@ -374,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();
@@ -381,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());
}
/**
@@ -434,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
new file mode 100644
index 0000000..21fac9b
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatUtils}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatUtilsTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatUtilsTest extends WindowTestsBase {
+
+ @Test
+ public void getLetterboxReasonString_inSizeCompatMode() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.setTopActivityInSizeCompatMode(/* inScm */ true);
+ });
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "SIZE_COMPAT_MODE");
+ });
+ }
+
+ @Test
+ public void getLetterboxReasonString_fixedOrientation() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+ /* forFixedOrientationAndAspectRatio */ true);
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "FIXED_ORIENTATION");
+ });
+ }
+
+ @Test
+ public void getLetterboxReasonString_isLetterboxedForDisplayCutout() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+ /* forFixedOrientationAndAspectRatio */ false);
+ robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ true);
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "DISPLAY_CUTOUT");
+ });
+ }
+
+ @Test
+ public void getLetterboxReasonString_aspectRatio() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+ /* forFixedOrientationAndAspectRatio */ false);
+ robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
+ robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ true);
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "ASPECT_RATIO");
+ });
+ }
+
+ @Test
+ public void getLetterboxReasonString_unknownReason() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponent();
+ a.checkTopActivityInSizeCompatMode(/* inScm */ false);
+ });
+ robot.setIsLetterboxedForFixedOrientationAndAspectRatio(
+ /* forFixedOrientationAndAspectRatio */ false);
+ robot.setIsLetterboxedForDisplayCutout(/* displayCutout */ false);
+ robot.setIsLetterboxedForAspectRatioOnly(/* forAspectRatio */ false);
+
+ robot.checkTopActivityLetterboxReason(/* expected */ "UNKNOWN_REASON");
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<AppCompatUtilsRobotTest> consumer) {
+ final AppCompatUtilsRobotTest robot = new AppCompatUtilsRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class AppCompatUtilsRobotTest extends AppCompatRobotBase {
+
+ private final WindowState mWindowState;
+
+ AppCompatUtilsRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ 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()
+ .isLetterboxedForFixedOrientationAndAspectRatio())
+ .thenReturn(forFixedOrientationAndAspectRatio);
+ }
+
+ void setIsLetterboxedForAspectRatioOnly(boolean forAspectRatio) {
+ when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
+ .isLetterboxedForAspectRatioOnly()).thenReturn(forAspectRatio);
+ }
+
+ void setIsLetterboxedForDisplayCutout(boolean displayCutout) {
+ when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout);
+ }
+
+ void checkTopActivityLetterboxReason(@NonNull String expected) {
+ Assert.assertEquals(expected,
+ AppCompatUtils.getLetterboxReasonString(activity().top(), mWindowState));
+ }
+
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index a159ce3..44837d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -58,6 +58,7 @@
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.util.ArraySet;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
@@ -72,6 +73,7 @@
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -612,6 +614,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION)
public void testTransitionHappensCancelNavigation() {
// Create a floating task and a fullscreen task, then navigating on fullscreen task.
// The navigation should not been cancelled when transition happens on floating task, and
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/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index bb5887d..58f8eb2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -33,6 +33,7 @@
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.os.Process;
import android.util.ArraySet;
@@ -40,12 +41,14 @@
import android.window.DisplayWindowPolicyController;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Set;
+import java.util.function.Supplier;
/**
* Tests for the {@link DisplayWindowPolicyController} class.
@@ -224,7 +227,7 @@
assertEquals(result, START_ABORTED);
}
- private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
+ private static class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
public ComponentName DISALLOWED_ACTIVITY =
new ComponentName("fake.package", "DisallowedActivity");
@@ -236,7 +239,8 @@
@Override
public boolean canActivityBeLaunched(@NonNull ActivityInfo activity, Intent intent,
@WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
- boolean isNewTask) {
+ boolean isNewTask, boolean isResultExpected,
+ @Nullable Supplier<IntentSender> intentSender) {
return canContainActivity(activity, windowingMode, launchingFromDisplayId, isNewTask);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 2e0d4d4..2f2b473 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -31,7 +31,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.testng.Assert.assertFalse;
@@ -357,8 +356,6 @@
@Test
public void testRemovesStaleDisplaySettings_defaultDisplay_removesStaleDisplaySettings() {
- assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
-
// Write density setting for second display then remove it.
final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
@@ -387,8 +384,6 @@
@Test
public void testRemovesStaleDisplaySettings_displayNotInLayout_keepsDisplaySettings() {
- assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
-
// Write density setting for primary display.
final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
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/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index fbc4c7b..ffaa2d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -66,7 +66,7 @@
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
() -> mAreCornersRounded, () -> Color.valueOf(mColor),
() -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
- /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {},
+ mock(AppCompatReachabilityPolicy.class),
() -> mParentSurface);
mTransaction = spy(StubTransaction.class);
}
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 61a6f31..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,98 +291,6 @@
}
@Test
- public void testIsVerticalThinLetterboxed() {
- // Vertical thin letterbox disabled
- doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
- .getThinLetterboxHeightPx();
- assertFalse(mController.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(mController.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(mController.isVerticalThinLetterboxed());
-
- // (task.width() - act.width()) / 2 = 10 = 10
- doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
- assertTrue(mController.isVerticalThinLetterboxed());
-
- // (task.width() - act.width()) / 2 = 11 > 10
- doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
- assertFalse(mController.isVerticalThinLetterboxed());
- }
-
- @Test
- public void testIsHorizontalThinLetterboxed() {
- // Horizontal thin letterbox disabled
- doReturn(-1).when(mActivity.mWmService.mAppCompatConfiguration)
- .getThinLetterboxWidthPx();
- assertFalse(mController.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(mController.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(mController.isHorizontalThinLetterboxed());
-
- // (task.height() - act.height()) / 2 = 10 = 10
- doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds();
- assertTrue(mController.isHorizontalThinLetterboxed());
-
- // (task.height() - act.height()) / 2 = 11 > 10
- doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds();
- assertFalse(mController.isHorizontalThinLetterboxed());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
- public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
- spyOn(mController);
- doReturn(true).when(mController).isVerticalThinLetterboxed();
- assertFalse(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(true).when(mController).isHorizontalThinLetterboxed();
- assertFalse(mController.allowHorizontalReachabilityForThinLetterbox());
-
- doReturn(false).when(mController).isVerticalThinLetterboxed();
- assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(false).when(mController).isHorizontalThinLetterboxed();
- assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
- }
-
- @Test
- @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
- public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
- spyOn(mController);
- doReturn(true).when(mController).isVerticalThinLetterboxed();
- assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(true).when(mController).isHorizontalThinLetterboxed();
- assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
-
- doReturn(false).when(mController).isVerticalThinLetterboxed();
- assertTrue(mController.allowVerticalReachabilityForThinLetterbox());
- doReturn(false).when(mController).isHorizontalThinLetterboxed();
- assertTrue(mController.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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index ed93a8c..aa997ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -281,7 +281,8 @@
if (horizontalReachability) {
final Consumer<Integer> doubleClick =
(Integer x) -> {
- mActivity.mLetterboxUiController.handleHorizontalDoubleTap(x);
+ mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+ .handleDoubleTap(x, displayHeight / 2);
mActivity.mRootWindowContainer.performSurfacePlacement();
};
@@ -310,7 +311,8 @@
} else {
final Consumer<Integer> doubleClick =
(Integer y) -> {
- mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
+ mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+ .handleDoubleTap(displayWidth / 2, y);
mActivity.mRootWindowContainer.performSurfacePlacement();
};
@@ -373,7 +375,8 @@
final Consumer<Integer> doubleClick =
(Integer y) -> {
- activity.mLetterboxUiController.handleVerticalDoubleTap(y);
+ activity.mAppCompatController.getAppCompatReachabilityPolicy()
+ .handleDoubleTap(dw / 2, y);
activity.mRootWindowContainer.performSurfacePlacement();
};
@@ -886,7 +889,7 @@
verify(mTask).onSizeCompatActivityChanged();
ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();
- assertTrue(taskInfo.appCompatTaskInfo.topActivityInSizeCompat);
+ assertTrue(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());
// Make the activity resizable again by restarting it
clearInvocations(mTask);
@@ -901,7 +904,7 @@
verify(mTask).onSizeCompatActivityChanged();
taskInfo = mTask.getTaskInfo();
- assertFalse(taskInfo.appCompatTaskInfo.topActivityInSizeCompat);
+ assertFalse(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());
}
@Test
@@ -919,7 +922,7 @@
verify(mTask).onSizeCompatActivityChanged();
ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();
- assertTrue(taskInfo.appCompatTaskInfo.topActivityInSizeCompat);
+ assertTrue(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());
// Create another Task to hold another size compat activity.
clearInvocations(mTask);
@@ -939,7 +942,7 @@
verify(mTask, never()).onSizeCompatActivityChanged();
taskInfo = secondTask.getTaskInfo();
- assertTrue(taskInfo.appCompatTaskInfo.topActivityInSizeCompat);
+ assertTrue(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());
}
@Test
@@ -3431,9 +3434,10 @@
mActivity.getWindowConfiguration().setBounds(null);
setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false);
-
- assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
- assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ assertFalse(reachabilityOverrides.isVerticalReachabilityEnabled());
+ assertFalse(reachabilityOverrides.isHorizontalReachabilityEnabled());
}
@Test
@@ -3456,7 +3460,8 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Horizontal reachability is disabled because the app is in split screen.
- assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3479,7 +3484,8 @@
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Vertical reachability is disabled because the app is in split screen.
- assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3501,7 +3507,8 @@
// Vertical reachability is disabled because the app does not match parent width
assertNotEquals(mActivity.getScreenResolvedBounds().width(),
mActivity.mDisplayContent.getBounds().width());
- assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3518,7 +3525,8 @@
assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
// Vertical reachability is still enabled as resolved bounds is not empty
- assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3535,7 +3543,8 @@
assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
// Horizontal reachability is still enabled as resolved bounds is not empty
- assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3549,7 +3558,8 @@
prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3563,7 +3573,8 @@
prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
SCREEN_ORIENTATION_LANDSCAPE);
- assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -3585,7 +3596,8 @@
// Horizontal reachability is disabled because the app does not match parent height
assertNotEquals(mActivity.getScreenResolvedBounds().height(),
mActivity.mDisplayContent.getBounds().height());
- assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3607,7 +3619,8 @@
// Horizontal reachability is enabled because the app matches parent height
assertEquals(mActivity.getScreenResolvedBounds().height(),
mActivity.mDisplayContent.getBounds().height());
- assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isHorizontalReachabilityEnabled());
}
@Test
@@ -3629,7 +3642,8 @@
// Vertical reachability is enabled because the app matches parent width
assertEquals(mActivity.getScreenResolvedBounds().width(),
mActivity.mDisplayContent.getBounds().width());
- assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+ .isVerticalReachabilityEnabled());
}
@Test
@@ -4306,15 +4320,17 @@
resizeDisplay(mTask.mDisplayContent, 1400, 2800);
// Make sure app doesn't jump to top (default tabletop position) when unfolding.
- assertEquals(1.0f, mActivity.mLetterboxUiController.getVerticalPositionMultiplier(
- mActivity.getParent().getConfiguration()), 0);
+ assertEquals(1.0f, mActivity.mAppCompatController
+ .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+ .getParent().getConfiguration()), 0);
// Simulate display fully open after unfolding.
setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
doReturn(false).when(mActivity.mDisplayContent).inTransition();
- assertEquals(1.0f, mActivity.mLetterboxUiController.getVerticalPositionMultiplier(
- mActivity.getParent().getConfiguration()), 0);
+ assertEquals(1.0f, mActivity.mAppCompatController
+ .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+ .getParent().getConfiguration()), 0);
}
@Test
@@ -4728,7 +4744,7 @@
assertTrue(mActivity.inSizeCompatMode());
assertEquals(mActivity.getState(), PAUSED);
assertTrue(mActivity.isVisible());
- assertTrue(mTask.getTaskInfo().appCompatTaskInfo.topActivityInSizeCompat);
+ assertTrue(mTask.getTaskInfo().appCompatTaskInfo.isTopActivityInSizeCompat());
}
/**
@@ -4809,10 +4825,12 @@
}
private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
- spyOn(mActivity.mLetterboxUiController);
- doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
+ final AppCompatReachabilityOverrides reachabilityOverrides =
+ mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+ spyOn(reachabilityOverrides);
+ doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
.allowVerticalReachabilityForThinLetterbox();
- doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController)
+ doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
.allowHorizontalReachabilityForThinLetterbox();
}
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/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index a232ff0..0a592f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -635,27 +635,27 @@
// The button should be eligible to be displayed
assertTrue(task.getTaskInfo()
- .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
+ .appCompatTaskInfo.eligibleForUserAspectRatioButton());
// When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
doReturn(false).when(
root.mAppCompatController.getAppCompatAspectRatioOverrides())
.shouldEnableUserAspectRatioSettings();
assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
+ .appCompatTaskInfo.eligibleForUserAspectRatioButton());
doReturn(true).when(root.mAppCompatController
.getAppCompatAspectRatioOverrides()).shouldEnableUserAspectRatioSettings();
// When in size compat mode the button is not enabled
doReturn(true).when(root).inSizeCompatMode();
assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
+ .appCompatTaskInfo.eligibleForUserAspectRatioButton());
doReturn(false).when(root).inSizeCompatMode();
// When the top activity is transparent, the button is not enabled
doReturn(false).when(root).fillsParent();
assertFalse(task.getTaskInfo()
- .appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton);
+ .appCompatTaskInfo.eligibleForUserAspectRatioButton());
doReturn(true).when(root).fillsParent();
}
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/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index fb81a52..2b611b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1708,7 +1708,7 @@
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
RunningTaskInfo info = infoCaptor.getValue();
assertEquals(rootTask.mTaskId, info.taskId);
- assertTrue(info.appCompatTaskInfo.topActivityInSizeCompat);
+ assertTrue(info.appCompatTaskInfo.isTopActivityInSizeCompat());
// Ensure task info show top activity that is not visible as not in size compat.
clearInvocations(organizer);
@@ -1718,7 +1718,7 @@
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
info = infoCaptor.getValue();
assertEquals(rootTask.mTaskId, info.taskId);
- assertFalse(info.appCompatTaskInfo.topActivityInSizeCompat);
+ assertFalse(info.appCompatTaskInfo.isTopActivityInSizeCompat());
// Ensure task info show non size compat top activity as not in size compat.
clearInvocations(organizer);
@@ -1729,7 +1729,7 @@
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
info = infoCaptor.getValue();
assertEquals(rootTask.mTaskId, info.taskId);
- assertFalse(info.appCompatTaskInfo.topActivityInSizeCompat);
+ assertFalse(info.appCompatTaskInfo.isTopActivityInSizeCompat());
}
@Test
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 b46189c..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,29 +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();
+ 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();
+ 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();
+ 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,
@@ -1399,9 +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();
+ 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,
@@ -1409,17 +1410,16 @@
waitHandlerIdle(mWm.mH);
assertThat(imeLayeringTargetOverlay.isVisible()).isFalse();
- assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
- assertThat(listener.mIsRemoved).isFalse();
- assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
+ 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();
+ verify(immi).setHasVisibleImeLayeringOverlay(false /* hasVisibleOverlay */,
+ mDisplayContent.getDisplayId());
}
@Test
@@ -1462,28 +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;
-
- @Override
- public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
- @WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
- mImeTargetToken = overlayWindowToken;
- mIsVisibleForImeTargetOverlay = visible;
- mIsRemoved = removed;
- }
-
- @Override
- public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed) {
- mImeTargetToken = imeInputTarget;
- mIsVisibleForImeInputTarget = visibleRequested;
- mIsRemoved = removed;
- }
- }
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1404413..6c1e1a4 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -80,9 +80,9 @@
import android.provider.Settings;
import android.service.usb.UsbDeviceManagerProto;
import android.service.usb.UsbHandlerProto;
+import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
-import android.text.TextUtils;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -880,7 +880,7 @@
}
}
- private void notifyAccessoryModeExit(int operationId) {
+ protected void notifyAccessoryModeExit(int operationId) {
// make sure accessory mode is off
// and restore default functions
Slog.d(TAG, "exited USB accessory mode");
@@ -2313,8 +2313,13 @@
*/
operationId = sUsbOperationCount.incrementAndGet();
if (msg.arg1 != 1) {
- // Set this since default function may be selected from Developer options
- setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
+ if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
+ notifyAccessoryModeExit(operationId);
+ } else {
+ // Set this since default function may be selected from Developer
+ // options
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
+ }
}
break;
case MSG_GADGET_HAL_REGISTERED:
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index 93cd291..a03d7e2 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -483,18 +483,28 @@
// check to see if these are recognized numbers, and use shortcuts if we can.
TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+
boolean isEmergencyNumber = false;
try {
isEmergencyNumber = tm.isEmergencyNumber(number);
- } catch (IllegalStateException ise) {
+ } catch (IllegalStateException | UnsupportedOperationException ise) {
// Ignore the exception that Telephony is not up. Use PhoneNumberUtils API now.
// Ideally the PhoneNumberUtils API needs to be removed once the
// telphony service not up issue can be fixed (b/187412989)
+ // UnsupportedOperationException: telephony.calling may not be supported on this device
isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(context, number);
}
+
+ boolean isVoicemailNumber;
+ try {
+ isVoicemailNumber = PhoneNumberUtils.isVoiceMailNumber(context, subId, number);
+ } catch (UnsupportedOperationException ex) {
+ isVoicemailNumber = false;
+ }
+
if (isEmergencyNumber) {
cw.event = EVENT_EMERGENCY_NUMBER;
- } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
+ } else if (isVoicemailNumber) {
cw.event = EVENT_VOICEMAIL_NUMBER;
} else {
cw.event = EVENT_NEW_QUERY;
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 94d4d22..9b83719 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -528,7 +528,7 @@
// current SMS app will already be the preferred activity - but checking whether or
// not this is true is just as expensive as reconfiguring the preferred activity so
// we just reconfigure every time.
- defaultSmsAppChanged(context);
+ grantPermissionsToSystemApps(context);
}
}
if (DEBUG_MULTIUSER) {
@@ -542,9 +542,9 @@
}
/**
- * Grants various permissions and appops on sms app change
+ * Grants various permissions and appops, e.g. on sms app change
*/
- private static void defaultSmsAppChanged(Context context) {
+ public static void grantPermissionsToSystemApps(Context context) {
PackageManager packageManager = context.getPackageManager();
AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
@@ -680,7 +680,7 @@
return;
}
- defaultSmsAppChanged(context);
+ grantPermissionsToSystemApps(context);
}
}
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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e57a95e..b9a001d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -51,6 +51,7 @@
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
+import android.telephony.satellite.SatelliteManager;
import com.android.internal.telephony.ICarrierConfigLoader;
import com.android.internal.telephony.flags.Flags;
@@ -9603,9 +9604,8 @@
* Defines the rules for data setup retry.
*
* The syntax of the retry rule:
- * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
- * are supported. If the capabilities are not specified, then the retry rule only applies
- * to the current failed APN used in setup data call request.
+ * 1. Retry based on {@link NetworkCapabilities}. If the capabilities are not specified, then
+ * the retry rule only applies to the current failed APN used in setup data call request.
* "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
*
* 2. Retry based on {@link DataFailCause}
@@ -10022,6 +10022,22 @@
"carrier_roaming_ntn_connect_type_int";
/**
+ * Indicates carrier roaming non-terrestrial network emergency call handover type that the
+ * device will use to perform a handover between ESOS or T911.
+ * If this key is set to {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS}
+ * then the handover will be made to ESOS. If this key is set to
+ * {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911} then the handover
+ * will be made to T911.
+ *
+ * The default value is {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911}.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String
+ KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT =
+ "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
+
+ /**
* The carrier roaming non-terrestrial network hysteresis time in seconds.
*
* If the device supports P2P satellite messaging which is defined by
@@ -11209,6 +11225,8 @@
(int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
+ sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
+ SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911);
sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
sDefaults.putInt(KEY_SATELLITE_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
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/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
index 9ff73e2..66a20ae 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
@@ -27,4 +27,11 @@
* @param state The current satellite modem state.
*/
void onSatelliteModemStateChanged(in int state);
+
+ /**
+ * Indicates that the satellite emergency mode has changed.
+ *
+ * @param isEmergency True means satellite enabled for emergency mode, false otherwise.
+ */
+ void onEmergencyModeChanged(in boolean isEmergency);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 4b83b65..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 {}
@@ -1531,6 +1540,12 @@
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onSatelliteModemStateChanged(state)));
}
+
+ @Override
+ public void onEmergencyModeChanged(boolean isEmergency) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onEmergencyModeChanged(isEmergency)));
+ }
};
sSatelliteModemStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
index 8d33c88..423a785 100644
--- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
@@ -35,4 +35,14 @@
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
+
+ /**
+ * Called when the satellite emergency mode has changed.
+ *
+ * @param isEmergency {@code true} enabled for emergency mode, {@code false} otherwise.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ default void onEmergencyModeChanged(boolean isEmergency) {};
}
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/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 7212beb..36bfbef 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -33,6 +33,8 @@
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.commons.ClassRemapper
+import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.util.CheckClassAdapter
import java.io.BufferedInputStream
import java.io.FileOutputStream
@@ -56,21 +58,23 @@
// 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.
- val filter = buildFilter(errors, allClasses, options)
+ val (filter, policyFileRemapper) = buildFilter(errors, allClasses, options)
// Transform the jar.
convert(
@@ -82,20 +86,25 @@
allClasses,
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")
}
}
@@ -107,7 +116,7 @@
errors: HostStubGenErrors,
allClasses: ClassNodes,
options: HostStubGenOptions,
- ): OutputFilter {
+ ): Pair<OutputFilter, Remapper?> {
// We build a "chain" of multiple filters here.
//
// The filters are build in from "inside", meaning the first filter created here is
@@ -160,10 +169,14 @@
filter,
)
+ var policyFileRemapper: Remapper? = null
+
// Next, "text based" filter, which allows to override polices without touching
// the target code.
options.policyOverrideFile.ifSet {
- filter = createFilterFromTextPolicyFile(it, allClasses, filter)
+ val (f, p) = createFilterFromTextPolicyFile(it, allClasses, filter)
+ filter = f
+ policyFileRemapper = p
}
// If `--intersect-stub-jar` is provided, load from these jar files too.
@@ -178,7 +191,7 @@
// Apply the implicit filter.
filter = ImplicitOutputFilter(errors, allClasses, filter)
- return filter
+ return Pair(filter, policyFileRemapper)
}
/**
@@ -205,35 +218,55 @@
classes: ClassNodes,
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,
- filter, packageRedirector, enableChecker, classes, errors,
- stats)
+ 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
+ )
+ 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 {
@@ -253,6 +286,7 @@
implOutStream: ZipOutputStream?,
filter: OutputFilter,
packageRedirector: PackageRedirectRemapper,
+ remapper: Remapper?,
enableChecker: Boolean,
classes: ClassNodes,
errors: HostStubGenErrors,
@@ -270,7 +304,7 @@
// If it's a class, convert it.
if (name.endsWith(".class")) {
processSingleClass(inZip, entry, stubOutStream, implOutStream, filter,
- packageRedirector, enableChecker, classes, errors, stats)
+ packageRedirector, remapper, enableChecker, classes, errors, stats)
return
}
@@ -321,6 +355,7 @@
implOutStream: ZipOutputStream?,
filter: OutputFilter,
packageRedirector: PackageRedirectRemapper,
+ remapper: Remapper?,
enableChecker: Boolean,
classes: ClassNodes,
errors: HostStubGenErrors,
@@ -332,16 +367,24 @@
log.d("Removing class: %s %s", classInternalName, classPolicy)
return
}
+ // If we're applying a remapper, we need to rename the file too.
+ var newName = entry.name
+ remapper?.mapType(classInternalName)?.let { remappedName ->
+ if (remappedName != classInternalName) {
+ log.d("Renaming class file: %s -> %s", classInternalName, remappedName)
+ newName = remappedName + ".class"
+ }
+ }
// Generate stub first.
if (stubOutStream != null && classPolicy.policy.needsInStub) {
log.v("Creating stub class: %s Policy: %s", classInternalName, classPolicy)
log.withIndent {
BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- val newEntry = ZipEntry(entry.name)
+ val newEntry = ZipEntry(newName)
stubOutStream.putNextEntry(newEntry)
convertClass(classInternalName, /*forImpl=*/false, bis,
- stubOutStream, filter, packageRedirector, enableChecker, classes,
- errors, null)
+ stubOutStream, filter, packageRedirector, remapper,
+ enableChecker, classes, errors, null)
stubOutStream.closeEntry()
}
}
@@ -350,11 +393,11 @@
log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy)
log.withIndent {
BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- val newEntry = ZipEntry(entry.name)
+ val newEntry = ZipEntry(newName)
implOutStream.putNextEntry(newEntry)
convertClass(classInternalName, /*forImpl=*/true, bis,
- implOutStream, filter, packageRedirector, enableChecker, classes,
- errors, stats)
+ implOutStream, filter, packageRedirector, remapper,
+ enableChecker, classes, errors, stats)
implOutStream.closeEntry()
}
}
@@ -371,6 +414,7 @@
out: OutputStream,
filter: OutputFilter,
packageRedirector: PackageRedirectRemapper,
+ remapper: Remapper?,
enableChecker: Boolean,
classes: ClassNodes,
errors: HostStubGenErrors,
@@ -387,6 +431,12 @@
if (enableChecker) {
outVisitor = CheckClassAdapter(outVisitor)
}
+
+ // Remapping should happen at the end.
+ remapper?.let {
+ outVisitor = ClassRemapper(outVisitor, remapper)
+ }
+
val visitorOptions = BaseAdapter.Options(
enablePreTrace = options.enablePreTrace.get,
enablePostTrace = options.enablePostTrace.get,
@@ -395,7 +445,7 @@
stats = stats,
)
outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter,
- packageRedirector, forImpl, visitorOptions)
+ packageRedirector, remapper, forImpl, visitorOptions)
cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
val data = cw.toByteArray()
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/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index a89824e..1828003 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -22,11 +22,13 @@
import com.android.hoststubgen.normalizeTextLine
import com.android.hoststubgen.whitespaceRegex
import org.objectweb.asm.Opcodes
+import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.tree.ClassNode
import java.io.BufferedReader
import java.io.FileReader
import java.io.PrintWriter
import java.util.Objects
+import java.util.regex.Pattern
/**
* Print a class node as a "keep" policy.
@@ -60,7 +62,7 @@
filename: String,
classes: ClassNodes,
fallback: OutputFilter,
- ): OutputFilter {
+ ): Pair<OutputFilter, Remapper?> {
log.i("Loading offloaded annotations from $filename ...")
log.withIndent {
val subclassFilter = SubclassFilter(classes, fallback)
@@ -73,6 +75,7 @@
var featureFlagsPolicy: FilterPolicyWithReason? = null
var syspropsPolicy: FilterPolicyWithReason? = null
var rFilePolicy: FilterPolicyWithReason? = null
+ val typeRenameSpec = mutableListOf<TextFilePolicyRemapper.TypeRenameSpec>()
try {
BufferedReader(FileReader(filename)).use { reader ->
@@ -251,6 +254,22 @@
imf.setRenameTo(className, fromName, signature, name)
}
}
+ "r", "rename" -> {
+ if (fields.size < 3) {
+ throw ParseException("Rename ('r') expects 2 fields.")
+ }
+ // Add ".*" to make it a prefix match.
+ val pattern = Pattern.compile(fields[1] + ".*")
+
+ // Removing the leading /'s from the prefix. This allows
+ // using a single '/' as an empty suffix, which is useful to have a
+ // "negative" rename rule to avoid subsequent raname's from getting
+ // applied. (Which is needed for services.jar)
+ val prefix = fields[2].trimStart('/')
+
+ typeRenameSpec += TextFilePolicyRemapper.TypeRenameSpec(
+ pattern, prefix)
+ }
else -> {
throw ParseException("Unknown directive \"${fields[0]}\"")
@@ -262,9 +281,16 @@
throw e.withSourceInfo(filename, lineNo)
}
+ var remapper: TextFilePolicyRemapper? = null
+ if (typeRenameSpec.isNotEmpty()) {
+ remapper = TextFilePolicyRemapper(typeRenameSpec)
+ }
+
// Wrap the in-memory-filter with AHF.
- return AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf)
+ return Pair(
+ AndroidHeuristicsFilter(
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf),
+ remapper)
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapper.kt
new file mode 100644
index 0000000..2d94bb4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapper.kt
@@ -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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.log
+import org.objectweb.asm.commons.Remapper
+import java.util.regex.Pattern
+
+/**
+ * A [Remapper] that provides a simple "jarjar" functionality.
+ */
+class TextFilePolicyRemapper(
+ val typeRenameSpecs: List<TypeRenameSpec>
+) : Remapper() {
+ /**
+ * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix]
+ * to it.
+ */
+ data class TypeRenameSpec(
+ val typeInternalNamePattern: Pattern,
+ val typeInternalNamePrefix: String,
+ )
+
+ private val cache = mutableMapOf<String, String>()
+
+ override fun mapType(typeInternalName: String): String {
+// if (typeInternalName == null) {
+// return null // do we need it??
+// }
+ cache[typeInternalName]?.let {
+ return it
+ }
+
+ var mapped: String = typeInternalName
+ typeRenameSpecs.forEach {
+ if (it.typeInternalNamePattern.matcher(typeInternalName).matches()) {
+ mapped = it.typeInternalNamePrefix + typeInternalName
+ log.d("Renaming type $typeInternalName to $mapped")
+ }
+ }
+ cache[typeInternalName] = mapped
+ return mapped
+ }
+
+ // TODO Do we need to implement mapPackage(), etc too?
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index c99ff0e..bad0449 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -34,6 +34,7 @@
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.ClassRemapper
+import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.util.TraceClassVisitor
import java.io.PrintWriter
@@ -259,13 +260,14 @@
companion object {
fun getVisitor(
- classInternalName: String,
- classes: ClassNodes,
- nextVisitor: ClassVisitor,
- filter: OutputFilter,
- packageRedirector: PackageRedirectRemapper,
- forImpl: Boolean,
- options: Options,
+ classInternalName: String,
+ classes: ClassNodes,
+ nextVisitor: ClassVisitor,
+ filter: OutputFilter,
+ packageRedirector: PackageRedirectRemapper,
+ remapper: Remapper?,
+ forImpl: Boolean,
+ options: Options,
): ClassVisitor {
var next = nextVisitor
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt
index b3790e1..e90ecd7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt
@@ -20,6 +20,18 @@
/**
* A [Remapper] for `--package-redirect`
+ *
+ * This is a feature to update all calls to specific packages to another package, which allows
+ * implementing a class in a different package, when the original package isn't allowed to modify.
+ *
+ * For example, using this, we can implement `dalvik.*` APIs in a separate package, and update
+ * all calls to the `dalvik` package to a different package.
+ *
+ * For this purpose, we don't expect the "renamed-from" classes to be in the target jar,
+ * so we don't apply the remapper to them. (The exclusion happens at the callsite of this class)
+ *
+ * TODO: Currently it's not used. Maybe remove, or just unify with the other remapper feature
+ * with TextFileFilterPolicyParser.kt.
*/
class PackageRedirectRemapper(
packageRedirects: List<Pair<String, String>>,
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 3ef1175..c127e67 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -2706,6 +2706,95 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+ Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 2, attributes: 2
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller;
+
+ public static int foo(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed."<init>":(I)V
+ x: invokevirtual #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 12 0 value I
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+ Compiled from "TinyFrameworkToBeRenamed.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 2
+ private final int mValue;
+ descriptor: I
+ flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field mValue:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 0 10 1 value I
+
+ public int getValue();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: getfield #x // Field mValue:I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
Compiled from "A.java"
public class com.android.hoststubgen.test.tinyframework.packagetest.A
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 0bbb418..17ba48c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -2177,6 +2177,56 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+ Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 2, attributes: 3
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int foo(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
Compiled from "A.java"
public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -2419,3 +2469,62 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+ Compiled from "TinyFrameworkToBeRenamed.java"
+public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 3
+ private final int mValue;
+ descriptor: I
+ flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int getValue();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 57f3783..0f5f7e7 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -3540,6 +3540,63 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+ Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 2, attributes: 3
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int foo(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed."<init>":(I)V
+ x: invokevirtual #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 12 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
Compiled from "A.java"
public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -3962,3 +4019,70 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+ Compiled from "TinyFrameworkToBeRenamed.java"
+public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 3
+ private final int mValue;
+ descriptor: I
+ flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=2, locals=2, args_size=2
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field mValue:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 0 10 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int getValue();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: getfield #x // Field mValue:I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 0bbb418..17ba48c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -2177,6 +2177,56 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+ Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 2, attributes: 3
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int foo(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
Compiled from "A.java"
public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -2419,3 +2469,62 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+ Compiled from "TinyFrameworkToBeRenamed.java"
+public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 2, attributes: 3
+ private final int mValue;
+ descriptor: I
+ flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=2, args_size=2
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int getValue();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=3, locals=1, args_size=1
+ x: new #x // class java/lang/RuntimeException
+ x: dup
+ x: ldc #x // String Stub!
+ x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+ x: athrow
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 91104de..3beea64 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -4408,6 +4408,83 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
+ Compiled from "TinyFrameworkRenamedClassCaller.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 3, attributes: 3
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ x: ldc #x // String <init>
+ x: ldc #x // String ()V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public static int foo(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ x: ldc #x // String foo
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: new #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed."<init>":(I)V
+ x: invokevirtual #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 12 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkRenamedClassCaller.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
Compiled from "A.java"
public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -5041,3 +5118,90 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
+ Compiled from "TinyFrameworkToBeRenamed.java"
+public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 1, methods: 3, attributes: 3
+ private final int mValue;
+ descriptor: I
+ flags: (0x0012) ACC_PRIVATE, ACC_FINAL
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+
+ public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
+ descriptor: (I)V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=2, args_size=2
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String <init>
+ x: ldc #x // String (I)V
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: aload_0
+ x: iload_1
+ x: putfield #x // Field mValue:I
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 11 10 1 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+ public int getValue();
+ descriptor: ()I
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String getValue
+ x: ldc #x // String ()I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: aload_0
+ x: getfield #x // Field mValue:I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+SourceFile: "TinyFrameworkToBeRenamed.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+ x: #x()
+ android.hosttest.annotation.HostSideTestWholeClassStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 530de43..75c9721 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -48,3 +48,9 @@
# The following rules are the same as above
# class com.android.hoststubgen.test.tinyframework.packagetest.A stub
# class com.android.hoststubgen.test.tinyframework.packagetest.sub.A stub
+
+
+# "rename" takes a type internal name, so '/'s is used as a separator.
+# The leading / in the prefix is not needed (it'll be stripped), but it's added to make
+# sure the stripping works.
+rename ^.*/TinyFrameworkToBeRenamed$ /rename_prefix/
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
new file mode 100644
index 0000000..31a164a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+@HostSideTestWholeClassStub
+public class TinyFrameworkRenamedClassCaller {
+ /** Calls the class that'll be renamed. */
+ public static int foo(int value) {
+ // When TinyFrameworkToBeRenamed gets renamed, this callsite should be updated too,
+ // so this code should work as-is.
+ return new TinyFrameworkToBeRenamed(value).getValue();
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
new file mode 100644
index 0000000..1430bcb
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+/**
+ * This class will be renamed by the "rename" directive in the policy file.
+ */
+@HostSideTestWholeClassStub
+public class TinyFrameworkToBeRenamed {
+ private final int mValue;
+
+ public TinyFrameworkToBeRenamed(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 37925e8..bf0f654 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -334,4 +334,9 @@
public void testRFileHeuristics() {
assertThat(Nested.ARRAY.length).isEqualTo(1);
}
+
+ @Test
+ public void testTypeRename() {
+ assertThat(TinyFrameworkRenamedClassCaller.foo(1)).isEqualTo(1);
+ }
}
diff --git a/tools/hoststubgen/scripts/dump-jar b/tools/hoststubgen/scripts/dump-jar
index 992665e..fe546fe 100755
--- a/tools/hoststubgen/scripts/dump-jar
+++ b/tools/hoststubgen/scripts/dump-jar
@@ -97,6 +97,7 @@
# - Remove the constant pool
# - Remove the line number table
# - Some other transient lines
+ # - Sometimes the javap shows mysterious warnings, so remove them too.
#
# `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without
# the start and the end lines.
@@ -106,7 +107,8 @@
-e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \
-e '/SHA-256 checksum/d' \
-e '/Last modified/d' \
- -e '/^Classfile jar/d'
+ -e '/^Classfile jar/d' \
+ -e '/\[warning\]/d'
else
cat # Print as-is.
fi
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
index cf0876a..0115339 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
@@ -55,6 +55,7 @@
.setLevel(
ProtoLogLevel.forNumber(log.logLevel.ordinal + 1))
.setGroupId(groupId)
+ .setLocation(log.position)
)
}
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/WifiBlobStore.java b/wifi/java/src/android/net/wifi/WifiBlobStore.java
index 8737c7e9..57d997b 100644
--- a/wifi/java/src/android/net/wifi/WifiBlobStore.java
+++ b/wifi/java/src/android/net/wifi/WifiBlobStore.java
@@ -16,8 +16,11 @@
package android.net.wifi;
+import android.os.Build;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.security.legacykeystore.ILegacyKeystore;
+import android.util.Log;
import com.android.internal.net.ConnectivityBlobStore;
@@ -26,13 +29,44 @@
* @hide
*/
public class WifiBlobStore extends ConnectivityBlobStore {
+ private static final String TAG = "WifiBlobStore";
private static final String DB_NAME = "WifiBlobStore.db";
private static final String LEGACY_KEYSTORE_SERVICE_NAME = "android.security.legacykeystore";
+ private static final boolean sIsVendorApiLevelGreaterThanT = isVendorApiLevelGreaterThanT();
private static WifiBlobStore sInstance;
private WifiBlobStore() {
super(DB_NAME);
}
+ private static boolean isVendorApiLevelGreaterThanT() {
+ int androidT = Build.VERSION_CODES.TIRAMISU; // redefine to avoid errorprone build issue
+ String[] vendorApiLevelProps = {
+ "ro.board.api_level", "ro.board.first_api_level", "ro.vndk.version"};
+ for (String propertyName : vendorApiLevelProps) {
+ int apiLevel = SystemProperties.getInt(propertyName, -1);
+ if (apiLevel != -1) {
+ Log.i(TAG, "Retrieved API level property, value=" + apiLevel);
+ return apiLevel > androidT;
+ }
+ }
+ // If none of the properties are defined, we are using the current API level (> V)
+ Log.i(TAG, "No API level properties are defined");
+ return true;
+ }
+
+ /**
+ * Check whether supplicant can access values stored in WifiBlobstore.
+ *
+ * NonStandardCertCallback was added in Android U, allowing supplicant to access the
+ * WifiKeystore APIs, which access WifiBlobstore. Previously, supplicant used
+ * WifiKeystoreHalConnector to access values stored in Legacy Keystore.
+ *
+ * @hide
+ */
+ public static boolean supplicantCanAccessBlobstore() {
+ return sIsVendorApiLevelGreaterThanT;
+ }
+
/** Returns an instance of WifiBlobStore. */
public static WifiBlobStore getInstance() {
if (sInstance == null) {
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 1a20a12..7df1d4b 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -574,9 +574,13 @@
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION)
+ @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.
*/
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index 5a214b7..90d13e6 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -19,11 +19,12 @@
}
flag {
- name: "legacy_keystore_to_wifi_blobstore_migration"
+ name: "legacy_keystore_to_wifi_blobstore_migration_read_only"
is_exported: true
namespace: "wifi"
description: "Add API to migrate all values from Legacy Keystore to the new Wifi Blobstore database"
bug: "332560152"
+ is_fixed_read_only: true
}
flag {