diff options
443 files changed, 12208 insertions, 5912 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 7d9e95bb12ee..4e34b63be0d6 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -172,6 +172,7 @@ cc_aconfig_library { // 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"], diff --git a/Ravenwood.bp b/Ravenwood.bp index 7faa33f8834e..5f32ba026b50 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -25,13 +25,15 @@ java_library { visibility: ["//visibility:private"], } -// Generate the stub/impl from framework-all, with hidden APIs. +// 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. +// 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"], srcs: [ ":framework-minus-apex-for-hoststubgen", @@ -41,148 +43,101 @@ genrule_defaults { ], out: [ "ravenwood.jar", - - // Following files are created just as FYI. - "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", ], - visibility: ["//visibility:private"], } +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: "$(location hoststubgen) " + - "--num-shards 6 --shard-index 0 " + // Only this line differs - - "@$(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) ", + 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: "$(location hoststubgen) " + - "--num-shards 6 --shard-index 1 " + // Only this line differs - - "@$(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) ", + 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: "$(location hoststubgen) " + - "--num-shards 6 --shard-index 2 " + // Only this line differs - - "@$(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) ", + 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: "$(location hoststubgen) " + - "--num-shards 6 --shard-index 3 " + // Only this line differs - - "@$(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) ", + 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: "$(location hoststubgen) " + - "--num-shards 6 --shard-index 4 " + // Only this line differs - - "@$(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) " + + cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 4", +} - "--out-impl-jar $(location ravenwood.jar) " + +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", +} - "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " + - "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) " + +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", +} - "--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_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_X5", + name: "framework-minus-apex.ravenwood-base_X8", defaults: ["framework-minus-apex.ravenwood-base_defaults"], - cmd: "$(location hoststubgen) " + - "--num-shards 6 --shard-index 5 " + // Only this line differs + cmd: framework_minus_apex_cmd + " --num-shards 10 --shard-index 8", +} - "@$(location :ravenwood-standard-options) " + +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", +} - "--debug-log $(location hoststubgen_framework-minus-apex.log) " + +// 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) " + - "--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) " + + "--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) ", + out: [ + "hoststubgen_framework-minus-apex_keep_all.txt", + "hoststubgen_framework-minus-apex_dump.txt", + "hoststubgen_framework-minus-apex_stats.csv", + "hoststubgen_framework-minus-apex_apis.csv", + ], } // Marge all the sharded jars @@ -198,73 +153,16 @@ java_genrule { ":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", ], } -// Merge the sharded text files -genrule { - name: "hoststubgen_framework-minus-apex_stats.csv", - defaults: ["ravenwood-internal-only-visibility-genrule"], - cmd: "cat $(in) > $(out)", - srcs: [ - ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_stats.csv}", - ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_stats.csv}", - ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_stats.csv}", - ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_stats.csv}", - ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_stats.csv}", - ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_stats.csv}", - ], - out: ["hoststubgen_framework-minus-apex_stats.csv"], -} - -genrule { - name: "hoststubgen_framework-minus-apex_apis.csv", - defaults: ["ravenwood-internal-only-visibility-genrule"], - cmd: "cat $(in) > $(out)", - srcs: [ - ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_apis.csv}", - ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_apis.csv}", - ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_apis.csv}", - ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_apis.csv}", - ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_apis.csv}", - ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_apis.csv}", - ], - out: ["hoststubgen_framework-minus-apex_apis.csv"], -} - -genrule { - name: "hoststubgen_framework-minus-apex_keep_all.txt", - defaults: ["ravenwood-internal-only-visibility-genrule"], - cmd: "cat $(in) > $(out)", - srcs: [ - ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_keep_all.txt}", - ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_keep_all.txt}", - ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_keep_all.txt}", - ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_keep_all.txt}", - ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_keep_all.txt}", - ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_keep_all.txt}", - ], - out: ["hoststubgen_framework-minus-apex_keep_all.txt"], -} - -genrule { - name: "hoststubgen_framework-minus-apex_dump.txt", - defaults: ["ravenwood-internal-only-visibility-genrule"], - cmd: "cat $(in) > $(out)", - srcs: [ - ":framework-minus-apex.ravenwood-base_X0{hoststubgen_framework-minus-apex_dump.txt}", - ":framework-minus-apex.ravenwood-base_X1{hoststubgen_framework-minus-apex_dump.txt}", - ":framework-minus-apex.ravenwood-base_X2{hoststubgen_framework-minus-apex_dump.txt}", - ":framework-minus-apex.ravenwood-base_X3{hoststubgen_framework-minus-apex_dump.txt}", - ":framework-minus-apex.ravenwood-base_X4{hoststubgen_framework-minus-apex_dump.txt}", - ":framework-minus-apex.ravenwood-base_X5{hoststubgen_framework-minus-apex_dump.txt}", - ], - out: ["hoststubgen_framework-minus-apex_dump.txt"], -} - java_library { name: "services.core-for-hoststubgen", installable: false, // host only jar. @@ -325,6 +223,9 @@ java_genrule { ], } +// 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"], @@ -337,7 +238,6 @@ java_library { // Jars in "ravenwood-runtime" are set to the classpath, sorted alphabetically. // Rename some of the dependencies to make sure they're included in the intended order. -// Also apply jarjar. java_library { name: "100-framework-minus-apex.ravenwood", defaults: ["ravenwood-internal-only-visibility-java"], diff --git a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java index c00c8d550885..06cd94263847 100644 --- a/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java +++ b/apct-tests/perftests/settingsprovider/src/android/provider/SettingsProviderPerfTest.java @@ -36,7 +36,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public final class SettingsProviderPerfTest { - private static final String NAMESPACE = "test@namespace"; + private static final String NAMESPACE = "testing"; private static final String SETTING_NAME1 = "test:setting1"; private static final String SETTING_NAME2 = "test-setting2"; private static final String UNSET_SETTING = "test_unset_setting"; 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 18ee6f2c7992..ba66ff72bfdd 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -4441,6 +4441,11 @@ public class AlarmManagerService extends SystemService { public void run() { ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); + synchronized (mLock) { + mLastTimeChangeClockTime = mInjector.getCurrentTimeMillis(); + mLastTimeChangeRealtime = mInjector.getElapsedRealtimeMillis(); + } + while (true) { int result = mInjector.waitForAlarm(); final long nowRTC = mInjector.getCurrentTimeMillis(); @@ -4464,10 +4469,9 @@ public class AlarmManagerService extends SystemService { expectedClockTime = lastTimeChangeClockTime + (nowELAPSED - mLastTimeChangeRealtime); } - if (lastTimeChangeClockTime == 0 || nowRTC < (expectedClockTime - 1000) + if (nowRTC < (expectedClockTime - 1000) || nowRTC > (expectedClockTime + 1000)) { - // The change is by at least +/- 1000 ms (or this is the first change), - // let's do it! + // The change is by at least +/- 1000 ms, let's do it! if (DEBUG_BATCH) { Slog.v(TAG, "Time changed notification from kernel; rebatching"); } diff --git a/config/Android.bp b/config/Android.bp index c9948c31f1c3..4a61cc7fa533 100644 --- a/config/Android.bp +++ b/config/Android.bp @@ -43,6 +43,6 @@ filegroup { prebuilt_etc { name: "dirty-image-objects", - src: "dirty-image-objects", - filename: "dirty-image-objects", + src: "dirty-image-objects.txt", + filename: "dirty-image-objects.txt", } diff --git a/config/OWNERS b/config/OWNERS index 6a5df76e96ab..916bf67dbd37 100644 --- a/config/OWNERS +++ b/config/OWNERS @@ -2,7 +2,7 @@ include /ZYGOTE_OWNERS # art-team@ manages the boot image profiles per-file boot-* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com -per-file dirty-image-objects = ishcheikin@google.com, ngeoffray@google.com, vmarko@google.com +per-file dirty-image-objects.txt = ishcheikin@google.com, ngeoffray@google.com, vmarko@google.com per-file generate-preloaded-classes.sh = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com per-file preloaded-classes* = islamelbanna@google.com, ngeoffray@google.com, vmarko@google.com diff --git a/config/README.md b/config/README.md index 450a5c695c82..0d9cbb68e9ee 100644 --- a/config/README.md +++ b/config/README.md @@ -5,7 +5,7 @@ * boot-profile.txt: An ordered list of methods from the boot classpath to be compiled by the JIT in the order provided in the file. Used by JIT zygote, when on-device signing failed. -* dirty-image-objects: List of objects in the boot image which are known to +* dirty-image-objects.txt: List of objects in the boot image which are known to become dirty. This helps binning objects in the image file. * preloaded-classes: classes that will be allocated in the boot image, and initialized by the zygote. diff --git a/config/dirty-image-objects b/config/dirty-image-objects deleted file mode 100644 index f2e2b82cd82a..000000000000 --- a/config/dirty-image-objects +++ /dev/null @@ -1,1728 +0,0 @@ -# -# Copyright (C) 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# -# -# Dirty-image-objects file for boot image. -# The image writer will bin these objects together in the image. -# More info about dirty objects format and how to collect the data can be -# found in: art/imgdiag/dirty_image_objects.md -# This particular file was generated by dumping all pre-installed apps. -# -Landroid/text/style/URLSpan; 0 -Landroid/content/res/Resources$NotFoundException; 1 -Landroid/os/PowerManager$WakeLock; 2 -Landroid/os/BatterySaverPolicyConfig; 2 -Landroid/content/ContextWrapper; 2 -Landroid/app/WallpaperInfo; 2 -Landroid/content/pm/PackageManager; 2 -Landroid/app/IWallpaperManager; 2 -Ljava/lang/BootClassLoader; 2 -Ljava/time/Duration; 2 -Landroid/util/Printer; 2 -Landroid/app/WallpaperManager$OnColorsChangedListener; 2 -Landroid/app/WallpaperColors; 2 -Landroid/content/pm/ServiceInfo; 2 -Landroid/app/KeyguardManager$KeyguardDismissCallback; 2 -Ljava/lang/CharSequence; 3 -Landroid/widget/Switch; 4 -Lcom/android/internal/util/ContrastColorUtil; 4 -Landroid/view/SurfaceControl; 4 -Landroid/graphics/ColorMatrix;.dexCache:Ljava/lang/Object; 4 -Lcom/android/internal/widget/CachingIconView; 4 -Landroid/window/IRemoteTransition$Stub$Proxy; 4 -Landroid/app/trust/TrustManager$TrustListener; 4 -Landroid/view/NotificationHeaderView; 4 -Lcom/android/internal/widget/ImageResolver; 4 -Landroid/window/WindowContainerTransaction$Change; 4 -Lcom/android/internal/widget/MessagingLayout; 4 -Ljava/util/concurrent/ConcurrentLinkedQueue; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4 -Landroid/view/RemotableViewMethod; 4 -Landroid/app/IApplicationThread$Stub$Proxy; 4 -Landroid/os/FileUtils; 4 -Landroid/view/View;.SCALE_X:Landroid/util/Property; 4 -Landroid/widget/GridLayout;.UNDEFINED_ALIGNMENT:Landroid/widget/GridLayout$Alignment; 4 -Landroid/media/MediaPlayer$EventHandler; 4 -Landroid/widget/DateTimeView; 4 -Llibcore/util/ZoneInfo; 4 -Lcom/android/internal/statusbar/IStatusBarService; 4 -Ljava/lang/invoke/MethodType;.internTable:Ljava/lang/invoke/MethodType$ConcurrentWeakInternSet;.stale:Ljava/lang/ref/ReferenceQueue; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4 -Lcom/android/internal/logging/UiEventLogger; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4 -Landroid/renderscript/RenderScript; 4 -Landroid/view/ViewTreeObserver$OnWindowVisibilityChangeListener; 4 -Lcom/android/internal/widget/RemeasuringLinearLayout; 4 -Landroid/widget/DateTimeView$ReceiverInfo$1; 4 -Landroid/view/View;.TRANSLATION_Y:Landroid/util/Property; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry; 4 -Lcom/android/internal/widget/NotificationExpandButton; 4 -Lcom/android/internal/view/menu/ActionMenuItemView; 4 -Landroid/view/animation/AnimationSet; 4 -Landroid/hardware/biometrics/BiometricSourceType;.FINGERPRINT:Landroid/hardware/biometrics/BiometricSourceType; 4 -Landroid/window/WindowOrganizer;.IWindowOrganizerControllerSingleton:Landroid/util/Singleton; 4 -Ljava/lang/Runnable; 4 -Lorg/apache/harmony/dalvik/ddmc/DdmServer;.mHandlerMap:Ljava/util/HashMap; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4 -Lcom/android/internal/widget/ImageFloatingTextView; 4 -Landroid/window/IWindowContainerToken$Stub$Proxy; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4 -Landroid/content/res/ColorStateList; 4 -Landroid/view/View;.SCALE_Y:Landroid/util/Property; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap; 4 -Lcom/android/internal/widget/ConversationLayout; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry;.right:Ljava/util/TreeMap$TreeMapEntry; 4 -Lcom/android/internal/colorextraction/ColorExtractor$OnColorsChangedListener; 4 -Landroid/hardware/face/FaceManager$FaceDetectionCallback; 4 -Landroid/widget/RemoteViews;.sLookupKey:Landroid/widget/RemoteViews$MethodKey; 4 -Landroid/widget/ViewSwitcher;.dexCache:Ljava/lang/Object; 4 -Lcom/android/internal/widget/NotificationActionListLayout; 4 -Ljava/util/concurrent/ConcurrentLinkedQueue$Node; 4 -Landroid/hardware/biometrics/BiometricSourceType;.FACE:Landroid/hardware/biometrics/BiometricSourceType; 4 -Landroid/hardware/biometrics/BiometricSourceType;.IRIS:Landroid/hardware/biometrics/BiometricSourceType; 4 -Landroid/view/NotificationTopLineView; 4 -Lcom/android/internal/protolog/BaseProtoLogImpl;.LOG_GROUPS:Ljava/util/TreeMap;.root:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry;.left:Ljava/util/TreeMap$TreeMapEntry; 4 -Landroid/widget/RemoteViews;.sMethods:Landroid/util/ArrayMap; 4 -Lcom/android/internal/os/BinderInternal$BinderProxyLimitListener; 5 -Landroid/app/AppOpsManager$OnOpNotedInternalListener; 5 -Lcom/android/internal/R$styleable;.WindowAnimation:[I 5 -Lcom/android/internal/logging/UiEventLogger$UiEventEnum; 5 -Lcom/android/internal/policy/AttributeCache; 5 -Landroid/app/Notification$CallStyle; 5 -Landroid/app/AppOpsManager$OnOpNotedListener; 5 -Lcom/android/internal/protolog/BaseProtoLogImpl; 5 -Landroid/app/AppOpsManager$OnOpStartedListener; 5 -Lcom/android/internal/util/ScreenshotHelper$1; 5 -Landroid/app/Notification$DecoratedCustomViewStyle; 5 -Landroid/view/DisplayCutout; 5 -Landroid/view/InputEvent;.mNextSeq:Ljava/util/concurrent/atomic/AtomicInteger; 5 -Lcom/android/internal/statusbar/NotificationVisibility; 5 -Landroid/telephony/DataSpecificRegistrationInfo; 6 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle; 7 -Landroid/telephony/VoiceSpecificRegistrationInfo; 8 -Landroid/telephony/AnomalyReporter; 8 -Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap; 8 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry; 8 -Landroid/app/LoadedApk$ServiceDispatcher$InnerConnection; 8 -Landroid/content/ContentProvider$Transport; 8 -Landroid/telephony/NetworkRegistrationInfo; 8 -Landroid/net/MatchAllNetworkSpecifier; 8 -Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 8 -Landroid/app/PropertyInvalidatedCache;.sInvalidates:Ljava/util/HashMap; 9 -Landroid/app/PropertyInvalidatedCache$NoPreloadHolder; 9 -Landroid/app/PropertyInvalidatedCache;.sDisabledKeys:Ljava/util/HashSet;.map:Ljava/util/HashMap; 10 -Landroid/media/AudioSystem$AudioRecordingCallback; 11 -Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedString; 11 -Landroid/net/metrics/IpManagerEvent; 11 -Lcom/android/internal/os/ProcessCpuTracker$FilterStats; 11 -Lcom/android/internal/infra/AbstractRemoteService$AsyncRequest; 11 -Landroid/content/pm/RegisteredServicesCache$3; 11 -Lcom/android/internal/os/LooperStats; 11 -Lcom/android/server/AppWidgetBackupBridge; 11 -Landroid/hardware/display/DisplayManagerInternal; 11 -Landroid/content/pm/PackageInfo; 11 -Landroid/hardware/soundtrigger/SoundTriggerModule$EventHandlerDelegate; 11 -Landroid/app/servertransaction/ResumeActivityItem; 11 -Lcom/android/internal/widget/AlertDialogLayout; 11 -Landroid/content/pm/FallbackCategoryProvider;.sFallbacks:Landroid/util/ArrayMap; 11 -Landroid/os/RemoteCallback$1; 11 -Landroid/content/pm/SharedLibraryInfo; 11 -Landroid/util/MemoryIntArray; 11 -Landroid/net/metrics/DhcpErrorEvent; 11 -Lcom/android/internal/util/function/DodecConsumer; 11 -Landroid/provider/Settings; 11 -Landroid/app/PropertyInvalidatedCache;.sCorkLock:Ljava/lang/Object; 11 -Lcom/android/internal/os/CachedDeviceState$Readonly; 11 -Landroid/app/job/JobServiceEngine$JobHandler; 11 -Landroid/app/SystemServiceRegistry; 11 -Lcom/android/internal/os/BinderInternal$CallStatsObserver; 11 -Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy; 11 -Landroid/hardware/location/IActivityRecognitionHardwareClient; 11 -Landroid/telecom/Logging/EventManager$EventListener; 11 -Landroid/accounts/AccountManagerInternal; 11 -Lcom/android/internal/os/KernelCpuBpfTracking; 11 -Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation; 11 -Landroid/hardware/camera2/CameraManager$CameraManagerGlobal; 11 -Landroid/os/ServiceSpecificException; 11 -Landroid/net/Uri$PathPart;.NULL:Landroid/net/Uri$PathPart; 11 -Landroid/app/ActivityManagerInternal; 11 -Landroid/media/AudioSystem; 11 -Landroid/service/dreams/DreamManagerInternal; 11 -Landroid/debug/AdbManagerInternal; 11 -Landroid/graphics/Bitmap$CompressFormat; 11 -Landroid/hardware/location/NanoAppMessage; 11 -Landroid/os/storage/StorageManagerInternal; 11 -Landroid/app/AppOpsManagerInternal; 11 -Ljava/security/cert/CertificateException; 11 -Ldalvik/system/VMRuntime; 11 -Landroid/content/pm/SigningInfo; 11 -Landroid/view/KeyEvent; 11 -Lcom/android/internal/view/WindowManagerPolicyThread; 11 -Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 11 -Landroid/content/res/ResourceTimer; 11 -Landroid/view/autofill/AutofillManagerInternal; 11 -Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 11 -Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool; 11 -Landroid/app/LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0; 11 -Lcom/android/server/LocalServices;.sLocalServiceObjects:Landroid/util/ArrayMap; 11 -Landroid/app/admin/DevicePolicyManagerInternal$OnCrossProfileWidgetProvidersChangeListener; 11 -Landroid/accounts/AccountManagerInternal$OnAppPermissionChangeListener; 11 -Landroid/content/pm/PermissionInfo; 11 -Landroid/view/WindowManagerPolicyConstants$PointerEventListener; 11 -Landroid/os/UEventObserver; 11 -Landroid/media/AudioManagerInternal$RingerModeDelegate; 11 -Landroid/view/Display$HdrCapabilities; 11 -Landroid/service/notification/Condition; 11 -Landroid/content/pm/UserPackage; 11 -Landroid/app/AppOpsManager$SamplingStrategy; 11 -Landroid/telephony/ServiceState; 11 -Landroid/app/servertransaction/PauseActivityItem; 11 -Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sMessageCallbacksPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool;.mLock:Ljava/lang/Object; 11 -Landroid/view/KeyCharacterMap$FallbackAction; 11 -Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringArray; 11 -Landroid/hardware/display/DeviceProductInfo; 11 -Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mHashes:[I 11 -Landroid/content/pm/RegisteredServicesCache$2; 11 -Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker; 11 -Landroid/app/PropertyInvalidatedCache;.sCorks:Ljava/util/HashMap; 11 -Landroid/service/notification/StatusBarNotification; 11 -Landroid/app/servertransaction/ConfigurationChangeItem; 11 -Landroid/app/ActivityManager$RecentTaskInfo; 11 -Landroid/app/Notification; 11 -Landroid/app/servertransaction/DestroyActivityItem; 11 -Landroid/webkit/WebViewLibraryLoader$RelroFileCreator; 11 -Landroid/net/metrics/NetworkEvent; 11 -Landroid/media/AudioPlaybackConfiguration; 11 -Landroid/accessibilityservice/AccessibilityServiceInfo; 11 -Landroid/hardware/display/DeviceProductInfo$ManufactureDate; 11 -Landroid/os/storage/StorageVolume; 11 -Landroid/os/BatteryManagerInternal; 11 -Landroid/appwidget/AppWidgetManagerInternal; 11 -Landroid/app/servertransaction/NewIntentItem; 11 -Landroid/content/pm/ShortcutServiceInternal; 11 -Landroid/app/assist/ActivityId; 11 -Landroid/window/DisplayAreaAppearedInfo; 11 -Landroid/os/Process;.ZYGOTE_PROCESS:Landroid/os/ZygoteProcess;.mLock:Ljava/lang/Object; 11 -Landroid/app/usage/UsageStats; 11 -Landroid/app/Notification$MediaStyle; 11 -Landroid/media/AudioSystem$DynamicPolicyCallback; 11 -Landroid/content/pm/ProviderInfo; 11 -Landroid/os/PowerManagerInternal; 11 -Landroid/service/voice/VoiceInteractionManagerInternal; 11 -Landroid/content/pm/FeatureInfo; 11 -Landroid/app/servertransaction/TopResumedActivityChangeItem; 11 -Landroid/app/Notification$DecoratedMediaCustomViewStyle; 11 -Landroid/appwidget/AppWidgetProviderInfo; 11 -Landroid/app/AppOpsManager$NoteOpEvent; 11 -Landroid/graphics/GraphicsStatsService; 11 -Landroid/view/DisplayAddress$Physical; 11 -Landroid/content/ComponentName$WithComponentName; 11 -Landroid/app/admin/DevicePolicyManagerInternal; 11 -Landroid/os/ResultReceiver$MyResultReceiver; 11 -Landroid/content/ContentProviderClient; 11 -Landroid/content/pm/RegisteredServicesCache$1; 11 -Landroid/app/PendingIntent$FinishedDispatcher; 11 -Landroid/location/LocationManager; 11 -Landroid/hardware/location/ContextHubInfo; 11 -Landroid/content/pm/ShortcutServiceInternal$ShortcutChangeListener; 11 -Lcom/android/server/usage/AppStandbyInternal; 11 -Landroid/content/pm/RegisteredServicesCacheListener; 11 -Landroid/app/servertransaction/LaunchActivityItem; 11 -Landroid/content/pm/BaseParceledListSlice$1; 11 -Landroid/annotation/StringRes; 11 -Lcom/android/internal/R$styleable;.Window:[I 11 -Landroid/service/notification/ZenModeConfig; 11 -Landroid/telecom/Logging/SessionManager$ISessionListener; 11 -Landroid/app/time/TimeZoneConfiguration; 11 -Landroid/net/metrics/ValidationProbeEvent; 11 -Landroid/content/pm/PackageInstaller$SessionInfo; 11 -Landroid/content/pm/UserPackage;.sCache:Landroid/util/SparseArrayMap;.mData:Landroid/util/SparseArray; 11 -Landroid/content/pm/PermissionGroupInfo; 11 -Landroid/hardware/sidekick/SidekickInternal; 11 -Lcom/android/internal/widget/ButtonBarLayout; 11 -Landroid/content/pm/LauncherActivityInfoInternal; 11 -Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap; 11 -Lcom/android/internal/widget/LockSettingsInternal; 11 -Landroid/media/AudioManagerInternal; 11 -Landroid/app/AppOpsManager$AttributedOpEntry; 11 -Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringList; 11 -Landroid/telecom/Log; 11 -Landroid/app/time/TimeZoneCapabilities; 11 -Landroid/attention/AttentionManagerInternal; 11 -Landroid/view/WindowManagerPolicyConstants; 11 -Landroid/content/pm/CrossProfileAppsInternal; 11 -Landroid/hardware/location/GeofenceHardwareService; 11 -Landroid/content/pm/dex/ArtManagerInternal; 11 -Landroid/net/metrics/IpReachabilityEvent; 11 -Landroid/content/pm/LauncherApps$ShortcutQuery$QueryFlags; 11 -Landroid/media/AudioAttributes; 11 -Landroid/app/PropertyInvalidatedCache$AutoCorker$1; 11 -Landroid/net/metrics/ApfProgramEvent; 11 -Landroid/content/pm/SigningDetails; 11 -Lcom/android/internal/protolog/ProtoLogImpl; 11 -Landroid/hardware/biometrics/ComponentInfoInternal; 11 -Lcom/android/internal/util/ToBooleanFunction; 11 -Landroid/app/ActivityThread$H; 11 -Landroid/hardware/location/GeofenceHardwareImpl; 11 -Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventHandler; 11 -Landroid/util/NtpTrustedTime; 11 -Landroid/hardware/soundtrigger/SoundTrigger$StatusListener; 11 -Lcom/android/internal/app/procstats/AssociationState;.sTmpSourceKey:Lcom/android/internal/app/procstats/AssociationState$SourceKey; 11 -Ljava/util/zip/ZipFile$ZipFileInflaterInputStream; 11 -Landroid/app/job/JobInfo; 11 -Lcom/android/internal/content/om/OverlayConfig; 11 -Landroid/webkit/WebViewZygote; 11 -Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringSet; 11 -Lcom/android/internal/infra/AbstractRemoteService$VultureCallback; 11 -Landroid/permission/PermissionManagerInternal; 11 -Lcom/android/server/WidgetBackupProvider; 11 -Landroid/window/WindowOnBackInvokedDispatcher$OnBackInvokedCallbackWrapper; 11 -Landroid/app/PropertyInvalidatedCache;.sCorkedInvalidates:Ljava/util/HashMap; 11 -Landroid/media/AudioPlaybackConfiguration$PlayerDeathMonitor; 11 -Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventCallback; 11 -Landroid/service/notification/NotificationListenerService$RankingMap; 11 -Landroid/os/UserHandle;.sExtraUserHandleCache:Landroid/util/SparseArray; 11 -Ljava/time/DateTimeException; 11 -Ljava/lang/NumberFormatException; 11 -Ljava/security/Provider;.knownEngines:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.125:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 11 -Landroid/app/LoadedApk$ServiceDispatcher$RunConnection; 11 -Landroid/view/RoundedCorners; 11 -Landroid/os/Process;.ZYGOTE_PROCESS:Landroid/os/ZygoteProcess; 11 -Landroid/media/audiopolicy/AudioVolumeGroup; 11 -Landroid/media/AudioSystem$ErrorCallback; 11 -Landroid/app/servertransaction/ActivityResultItem; 11 -Lcom/android/internal/widget/DialogTitle; 11 -Lcom/android/internal/os/StoragedUidIoStatsReader$Callback; 11 -Landroid/view/ViewRootImpl$W; 11 -Landroid/app/ServiceStartArgs; 11 -Landroid/window/TaskAppearedInfo; 11 -Lcom/android/internal/listeners/ListenerExecutor$FailureCallback; 11 -Landroid/app/ApplicationExitInfo; 11 -Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker;.mLock:Ljava/lang/Object; 11 -Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringValueMap; 11 -Landroid/content/pm/ResolveInfo; 11 -Lcom/android/internal/display/BrightnessSynchronizer; 11 -Landroid/window/IOnBackInvokedCallback$Stub$Proxy; 12 -Landroid/graphics/drawable/PictureDrawable; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.126:Ljava/lang/Byte; 13 -Landroid/view/ViewDebug$ExportedProperty; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.41:Ljava/lang/Byte; 13 -Landroid/view/inputmethod/DeleteGesture; 13 -Landroid/view/ViewDebug$IntToString; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.56:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.65:Ljava/lang/Byte; 13 -Landroid/webkit/WebViewFactory;.sProviderLock:Ljava/lang/Object; 13 -Ljava/lang/IllegalAccessError; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.51:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.52:Ljava/lang/Byte; 13 -Landroid/view/inputmethod/DeleteRangeGesture; 13 -Landroid/window/WindowContext; 13 -Ljava/util/concurrent/ConcurrentSkipListMap$Node; 13 -Landroid/view/inputmethod/SelectRangeGesture; 13 -Landroid/util/MalformedJsonException; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.131:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.120:Ljava/lang/Byte; 13 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 13 -Ljava/nio/file/StandardOpenOption;.TRUNCATE_EXISTING:Ljava/nio/file/StandardOpenOption; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.121:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.16:Ljava/lang/Byte; 13 -Ljava/util/concurrent/ConcurrentSkipListMap$Index; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.139:Ljava/lang/Byte; 13 -Landroid/view/ViewDebug$FlagToString; 13 -Landroid/view/inputmethod/SelectGesture; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.20:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.94:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.64:Ljava/lang/Byte; 13 -Landroid/webkit/WebViewFactoryProvider$Statics; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.95:Ljava/lang/Byte; 13 -Landroid/service/media/MediaBrowserService$ServiceBinder$1; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.7:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.23:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.46:Ljava/lang/Byte; 13 -Landroid/provider/Settings$SettingNotFoundException; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.74:Ljava/lang/Byte; 13 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.8:Ljava/lang/Byte; 13 -Landroid/widget/TextView;.TEMP_POSITION:[F 13 -Ljava/io/ByteArrayInputStream; 14 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.93:Ljava/lang/Byte; 14 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.134:Ljava/lang/Byte; 14 -Landroid/text/style/ImageSpan; 14 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.154:Ljava/lang/Byte; 15 -Landroid/view/TextureView$SurfaceTextureListener; 16 -Landroid/media/AudioManager$OnAudioFocusChangeListener; 17 -Ljava/util/Locale;.JAPAN:Ljava/util/Locale; 18 -Ljava/util/Locale;.GERMANY:Ljava/util/Locale; 19 -Ljava/util/Locale;.CANADA_FRENCH:Ljava/util/Locale; 20 -Ljava/util/Locale;.ITALY:Ljava/util/Locale; 20 -Ljava/util/Locale;.FRANCE:Ljava/util/Locale; 20 -Ljava/util/Locale;.UK:Ljava/util/Locale; 21 -Ljava/util/Locale;.CANADA:Ljava/util/Locale; 21 -Ljava/util/Locale$Cache;.LOCALECACHE:Ljava/util/Locale$Cache;.map:Ljava/util/concurrent/ConcurrentMap; 22 -Ljava/lang/IllegalStateException; 23 -Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sMessageCallbacksPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool; 24 -Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sMessageCallbacksPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool;.mPool:[Ljava/lang/Object; 24 -Landroid/media/MediaRouter$WifiDisplayStatusChangedReceiver; 25 -Landroid/media/MediaRouter$VolumeChangeReceiver; 25 -Landroid/app/AppOpsManager$OnOpActiveChangedListener; 26 -Landroid/media/PlayerBase; 27 -Landroid/content/pm/Checksum$Type; 28 -Ljava/lang/Class; 29 -Landroid/widget/MediaController$MediaPlayerControl; 30 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.135:Ljava/lang/Long; 30 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.152:Ljava/lang/Long; 30 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.215:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.206:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.137:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.203:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.213:Ljava/lang/Byte; 31 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.549:Ljava/lang/Long; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.201:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.249:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.163:Ljava/lang/Byte; 31 -Ljava/util/HashMap; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.210:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.161:Ljava/lang/Byte; 31 -Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.0:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.199:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.248:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.252:Ljava/lang/Byte; 31 -Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.3:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.159:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.217:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.200:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.240:Ljava/lang/Byte; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.198:Ljava/lang/Byte; 31 -Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.4:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 31 -Landroid/content/pm/PackageManager$OnChecksumsReadyListener; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.193:Ljava/lang/Byte; 31 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.228:Ljava/lang/Long; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.236:Ljava/lang/Byte; 31 -Landroid/telephony/ims/ImsService;.CAPABILITIES_LOG_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.2:Ljava/lang/Long; 31 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.211:Ljava/lang/Byte; 31 -Landroid/view/SurfaceView; 32 -Landroid/view/ViewStub$OnInflateListener; 33 -Landroid/graphics/drawable/DrawableInflater;.CONSTRUCTOR_MAP:Ljava/util/HashMap; 34 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.245:Ljava/lang/Byte; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.232:Ljava/lang/Byte; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.12:Ljava/lang/Byte; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.170:Ljava/lang/Long; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.183:Ljava/lang/Long; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.246:Ljava/lang/Byte; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.168:Ljava/lang/Long; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.72:Ljava/lang/Byte; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.243:Ljava/lang/Byte; 35 -Ljava/util/WeakHashMap;.NULL_KEY:Ljava/lang/Object; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.235:Ljava/lang/Byte; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.147:Ljava/lang/Long; 35 -Ljava/io/InterruptedIOException; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.184:Ljava/lang/Long; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.165:Ljava/lang/Long; 35 -Landroid/text/style/ForegroundColorSpan; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.176:Ljava/lang/Long; 35 -Ljava/lang/Long$LongCache;.archivedCache:[Ljava/lang/Long;.173:Ljava/lang/Long; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.181:Ljava/lang/Byte; 35 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.157:Ljava/lang/Byte; 35 -Landroid/content/res/AssetManager$AssetInputStream; 35 -Landroid/graphics/drawable/TransitionDrawable; 36 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1:Ljava/lang/Boolean; 37 -Landroid/view/ViewOverlay$OverlayViewGroup; 38 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.11:Ljava/lang/Boolean; 39 -Ljava/util/Observer; 40 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.129:Ljava/lang/Byte; 41 -[Ljava/lang/Byte; 41 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.144:Ljava/lang/Byte; 41 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.164:Ljava/lang/Byte; 42 -Landroid/view/OrientationEventListener; 43 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.195:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.233:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.229:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.128:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.242:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.196:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.208:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.212:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.228:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.205:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.197:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.204:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.207:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.223:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.244:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.174:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.194:Ljava/lang/Byte; 44 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.225:Ljava/lang/Byte; 45 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.239:Ljava/lang/Byte; 45 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.238:Ljava/lang/Byte; 45 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.227:Ljava/lang/Byte; 45 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.152:Ljava/lang/Byte; 46 -Landroid/app/RemoteAction; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.168:Ljava/lang/Byte; 46 -Landroid/text/style/QuoteSpan; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.54:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.124:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.142:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.190:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.114:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.69:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.30:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.133:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.49:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.58:Ljava/lang/Byte; 46 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.143:Ljava/lang/Byte; 47 -Landroid/icu/text/RelativeDateTimeFormatter$AbsoluteUnit; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.82:Ljava/lang/Byte; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.140:Ljava/lang/Byte; 47 -Landroid/icu/text/RelativeDateTimeFormatter;.fallbackCache:[Landroid/icu/text/RelativeDateTimeFormatter$Style; 47 -Landroid/icu/text/RelativeDateTimeFormatter$Style; 47 -Landroid/icu/text/RelativeDateTimeFormatter;.cache:Landroid/icu/text/RelativeDateTimeFormatter$Cache;.cache:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 47 -Landroid/icu/text/RelativeDateTimeFormatter$RelativeUnit; 47 -Landroid/icu/text/RelativeDateTimeFormatter$Direction; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.130:Ljava/lang/Byte; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.43:Ljava/lang/Byte; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.146:Ljava/lang/Byte; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.138:Ljava/lang/Byte; 47 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.136:Ljava/lang/Byte; 48 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.0:Ljava/lang/Byte; 49 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.160:Ljava/lang/Byte; 49 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.169:Ljava/lang/Byte; 50 -Landroid/widget/Spinner; 50 -Landroid/widget/MultiAutoCompleteTextView; 50 -Ljava/util/ArrayList; 50 -Landroid/widget/CheckBox; 50 -Ljava/io/Serializable; 50 -Landroid/widget/RatingBar; 50 -Ljava/lang/Byte$ByteCache;.archivedCache:[Ljava/lang/Byte;.132:Ljava/lang/Byte; 50 -Landroid/widget/AutoCompleteTextView; 50 -Ljava/util/concurrent/ConcurrentLinkedDeque$Node; 50 -[Ljava/lang/Object; 50 -Landroid/widget/SeekBar; 51 -Ljava/lang/Void; 52 -Landroid/app/ActivityTaskManager;.sInstance:Landroid/util/Singleton; 53 -Landroid/view/ViewRootImpl$$ExternalSyntheticLambda11; 54 -Landroid/view/ViewTreeObserver$OnWindowFocusChangeListener; 55 -Landroid/view/InsetsAnimationThread; 56 -Lcom/android/internal/jank/InteractionJankMonitor$InstanceHolder; 57 -Lcom/android/internal/jank/InteractionJankMonitor; 57 -Landroid/hardware/camera2/CameraCharacteristics;.FLASH_INFO_AVAILABLE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 58 -Landroid/hardware/display/NightDisplayListener$Callback; 59 -Landroid/media/MediaRouter2Manager; 59 -Landroid/os/HandlerExecutor; 59 -Landroid/os/strictmode/LeakedClosableViolation; 60 -Lcom/android/internal/logging/MetricsLogger; 60 -Lcom/android/internal/os/PowerProfile;.sPowerItemMap:Ljava/util/HashMap; 61 -Lcom/android/internal/os/PowerProfile;.sPowerArrayMap:Ljava/util/HashMap; 61 -Lcom/android/internal/os/PowerProfile;.sModemPowerProfile:Lcom/android/internal/power/ModemPowerProfile;.mPowerConstants:Landroid/util/SparseDoubleArray;.mValues:Landroid/util/SparseLongArray; 61 -Landroid/content/IntentFilter; 62 -Landroid/telecom/TelecomManager; 63 -Ljava/lang/IllegalArgumentException; 64 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 65 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 65 -Landroid/telephony/VisualVoicemailSmsFilterSettings;.DEFAULT_ORIGINATING_NUMBERS:Ljava/util/List; 66 -Ljava/util/AbstractList; 68 -Ljava/util/AbstractCollection; 68 -Ljava/util/Collections$EmptyList; 69 -Ljava/lang/StackTraceElement; 69 -[Ljava/lang/StackTraceElement; 69 -Landroid/os/strictmode/Violation; 70 -Ljava/util/List; 71 -Ljava/lang/String; 72 -Ljava/io/ObjectInputStream; 73 -Ljava/io/ObjectStreamClass$Caches;.localDescs:Ljava/util/concurrent/ConcurrentMap; 73 -Ljava/io/ObjectStreamClass$Caches;.reflectors:Ljava/util/concurrent/ConcurrentMap; 73 -Ljava/io/ObjectOutputStream; 73 -Ljava/lang/Number; 74 -Ljava/math/BigInteger; 75 -[B 76 -Landroid/os/Handler; 77 -Landroid/view/accessibility/AccessibilityManager; 78 -Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mKeys:[I 79 -Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 79 -Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray; 79 -Landroid/widget/FrameLayout; 80 -Lcom/android/internal/inputmethod/ImeTracing; 80 -Lcom/android/internal/policy/DecorView; 80 -Landroid/view/accessibility/AccessibilityNodeIdManager; 80 -Landroid/view/ViewTreeObserver; 80 -Landroid/view/ViewRootImpl; 80 -Landroid/os/SystemProperties;.sChangeCallbacks:Ljava/util/ArrayList; 80 -Landroid/transition/ChangeTransform; 80 -Landroid/window/SurfaceSyncGroup; 80 -Landroid/transition/ChangeClipBounds; 80 -Landroid/view/SurfaceControlRegistry; 80 -Landroid/transition/ChangeImageTransform; 80 -Landroid/widget/LinearLayout; 80 -Landroid/view/ViewStub; 81 -Landroid/text/TextLine;.sCached:[Landroid/text/TextLine; 82 -Landroid/text/TextUtils; 82 -Landroid/graphics/TemporaryBuffer; 82 -Landroid/content/res/ColorStateList;.sCache:Landroid/util/SparseArray; 83 -Landroid/text/Layout;.sTempRect:Landroid/graphics/Rect; 84 -Landroid/widget/ImageView; 85 -Landroid/graphics/drawable/ColorDrawable; 86 -Landroid/os/StrictMode$InstanceTracker;.sInstanceCounts:Ljava/util/HashMap; 87 -Landroid/app/ActivityClient;.INTERFACE_SINGLETON:Landroid/app/ActivityClient$ActivityClientControllerSingleton; 88 -Landroid/app/ActivityClient;.sInstance:Landroid/util/Singleton; 88 -Landroid/view/AbsSavedState$1; 89 -Landroid/app/FragmentManagerState; 90 -Landroid/window/OnBackAnimationCallback; 91 -Landroid/animation/AnimatorInflater;.sTmpTypedValue:Landroid/util/TypedValue; 92 -Landroid/graphics/drawable/RippleDrawable; 93 -Landroid/view/inputmethod/IInputMethodManagerGlobalInvoker; 94 -Landroid/app/ActivityTaskManager;.IActivityTaskManagerSingleton:Landroid/util/Singleton; 95 -Landroid/view/Choreographer; 96 -Lcom/android/internal/os/SomeArgs; 97 -Landroid/graphics/Bitmap; 98 -Landroid/view/autofill/AutofillId; 99 -Landroid/view/inputmethod/BaseInputConnection;.COMPOSING:Ljava/lang/Object; 100 -Landroid/text/Selection;.SELECTION_MEMORY:Ljava/lang/Object; 101 -Landroid/text/Selection;.SELECTION_END:Ljava/lang/Object; 101 -Landroid/text/Selection;.SELECTION_START:Ljava/lang/Object; 101 -Landroid/text/SpannableStringBuilder;.sCachedIntBuffer:[[I 102 -Landroid/text/Selection$MemoryTextWatcher; 103 -Landroid/text/SpanWatcher; 104 -Lcom/android/internal/util/ArrayUtils;.sCache:[Ljava/lang/Object; 105 -Ljava/lang/Integer;.SMALL_NEG_VALUES:[Ljava/lang/String; 106 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 107 -Lsun/nio/ch/SharedFileLockTable;.lockMap:Ljava/util/concurrent/ConcurrentHashMap; 108 -Lsun/nio/ch/FileChannelImpl; 108 -Landroid/database/sqlite/SQLiteDatabase$CursorFactory; 109 -Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder; 110 -Landroid/database/sqlite/SQLiteCompatibilityWalFlags; 110 -Landroid/database/sqlite/SQLiteGlobal; 110 -Landroid/database/CursorWindow; 111 -Landroid/content/ContentResolver; 112 -Ljava/nio/charset/Charset; 113 -Landroid/app/ContextImpl; 114 -Ljava/util/concurrent/Executors$DefaultThreadFactory;.poolNumber:Ljava/util/concurrent/atomic/AtomicInteger; 115 -Landroid/content/pm/PackageManager;.sPackageInfoCache:Landroid/app/PropertyInvalidatedCache;.mCache:Ljava/util/LinkedHashMap; 116 -Landroid/content/pm/PackageManager;.sApplicationInfoCache:Landroid/app/PropertyInvalidatedCache;.mCache:Ljava/util/LinkedHashMap; 117 -Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.systemContext:Ljava/util/logging/LogManager$LoggerContext;.namedLoggers:Ljava/util/Hashtable;.table:[Ljava/util/Hashtable$HashtableEntry; 118 -Landroid/ddm/DdmHandleAppName; 118 -Landroid/provider/DeviceConfigInitializer; 118 -Lsun/misc/Cleaner; 118 -Ldalvik/system/CloseGuard; 118 -Landroid/graphics/Typeface; 118 -Landroid/os/BinderProxy;.sProxyMap:Landroid/os/BinderProxy$ProxyMap;.mMainIndexKeys:[[Ljava/lang/Long; 118 -Landroid/permission/PermissionManager; 118 -Landroid/media/MediaFrameworkPlatformInitializer; 118 -Ljava/util/TimeZone; 118 -Landroid/os/Environment; 118 -Landroid/compat/Compatibility; 118 -Landroid/os/ServiceManager; 118 -Landroid/content/pm/PackageManager;.sApplicationInfoCache:Landroid/app/PropertyInvalidatedCache; 118 -Ljava/util/Locale$NoImagePreloadHolder; 118 -Ljava/lang/System; 118 -Lcom/android/internal/os/RuntimeInit; 118 -Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.systemContext:Ljava/util/logging/LogManager$LoggerContext;.namedLoggers:Ljava/util/Hashtable; 118 -Ldalvik/system/VMRuntime;.THE_ONE:Ldalvik/system/VMRuntime; 118 -Landroid/view/View; 118 -Landroid/hardware/display/DisplayManagerGlobal; 118 -Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager; 118 -Landroid/telephony/TelephonyFrameworkInitializer; 118 -Landroid/se/omapi/SeFrameworkInitializer; 118 -Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mHashes:[I 118 -Landroid/security/net/config/SystemCertificateSource$NoPreloadHolder; 118 -Landroid/security/net/config/ApplicationConfig; 118 -Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map; 118 -Ljava/util/Locale; 118 -Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.table:[Ljava/util/WeakHashMap$Entry; 118 -Ljava/security/Provider; 118 -Ldalvik/system/ZygoteHooks; 118 -Landroid/os/Message; 118 -Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 118 -Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap; 118 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap; 118 -Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.userContext:Ljava/util/logging/LogManager$LoggerContext;.namedLoggers:Ljava/util/Hashtable; 118 -Ljava/lang/ThreadGroup;.mainThreadGroup:Ljava/lang/ThreadGroup; 118 -Ldalvik/system/RuntimeHooks; 118 -Landroid/nfc/NfcFrameworkInitializer; 118 -Landroid/os/Looper; 118 -Landroid/os/LocaleList; 118 -Ldalvik/system/SocketTagger; 118 -Landroid/icu/util/TimeZone; 118 -Landroid/util/ArraySet; 118 -Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.systemContext:Ljava/util/logging/LogManager$LoggerContext;.root:Ljava/util/logging/LogManager$LogNode; 118 -Landroid/os/BinderProxy;.sProxyMap:Landroid/os/BinderProxy$ProxyMap;.mMainIndexValues:[Ljava/util/ArrayList; 118 -Ljava/util/Random;.seedUniquifier:Ljava/util/concurrent/atomic/AtomicLong; 118 -Landroid/app/ActivityThread; 118 -Landroid/os/Binder; 118 -Ljava/lang/ThreadLocal;.nextHashCode:Ljava/util/concurrent/atomic/AtomicInteger; 119 -Landroid/os/Parcel; 120 -Landroid/system/UnixSocketAddress; 120 -Ljava/lang/ThreadGroup;.systemThreadGroup:Ljava/lang/ThreadGroup; 120 -Ljava/lang/Daemons$FinalizerDaemon;.INSTANCE:Ljava/lang/Daemons$FinalizerDaemon; 120 -Landroid/os/Parcel;.sPairedCreators:Ljava/util/HashMap; 120 -Ljava/lang/Thread; 120 -Landroid/os/Parcel;.mCreators:Ljava/util/HashMap; 120 -Ljava/lang/Daemons$FinalizerDaemon;.INSTANCE:Ljava/lang/Daemons$FinalizerDaemon;.progressCounter:Ljava/util/concurrent/atomic/AtomicInteger; 120 -Landroid/system/StructPollfd; 120 -Ljava/lang/Daemons$HeapTaskDaemon;.INSTANCE:Ljava/lang/Daemons$HeapTaskDaemon; 120 -Landroid/system/StructTimeval; 120 -Ldalvik/system/VMRuntime;.THE_ONE:Ldalvik/system/VMRuntime;.allocationCount:Ljava/util/concurrent/atomic/AtomicInteger; 120 -Ljava/lang/Daemons$ReferenceQueueDaemon;.INSTANCE:Ljava/lang/Daemons$ReferenceQueueDaemon;.progressCounter:Ljava/util/concurrent/atomic/AtomicInteger; 120 -Landroid/os/GraphicsEnvironment;.sInstance:Landroid/os/GraphicsEnvironment; 120 -Ljava/lang/Daemons$FinalizerWatchdogDaemon;.INSTANCE:Ljava/lang/Daemons$FinalizerWatchdogDaemon; 120 -Ljava/lang/ref/FinalizerReference; 120 -Landroid/os/Process; 120 -Ljava/lang/Daemons$ReferenceQueueDaemon;.INSTANCE:Ljava/lang/Daemons$ReferenceQueueDaemon; 120 -Lcom/android/internal/os/BinderInternal; 120 -Landroid/app/ApplicationLoaders;.gApplicationLoaders:Landroid/app/ApplicationLoaders;.mLoaders:Landroid/util/ArrayMap; 121 -Landroid/app/DexLoadReporter;.INSTANCE:Landroid/app/DexLoadReporter;.mDataDirs:Ljava/util/Set;.map:Ljava/util/HashMap; 122 -Ldalvik/system/BaseDexClassLoader; 122 -Landroid/renderscript/RenderScriptCacheDir; 122 -Landroid/graphics/Compatibility; 123 -Llibcore/io/Libcore; 123 -Landroid/provider/FontsContract; 123 -Ljava/security/Security;.version:Ljava/util/concurrent/atomic/AtomicInteger; 123 -Llibcore/net/NetworkSecurityPolicy; 123 -Lsun/security/jca/Providers; 123 -Landroid/graphics/Canvas; 123 -Landroid/os/StrictMode; 124 -Landroid/content/pm/PackageManager;.sPackageInfoCache:Landroid/app/PropertyInvalidatedCache; 125 -Lcom/android/internal/os/StatsdHiddenApiUsageLogger;.sInstance:Lcom/android/internal/os/StatsdHiddenApiUsageLogger; 126 -Ljava/util/logging/LogManager;.manager:Ljava/util/logging/LogManager;.loggerRefQueue:Ljava/lang/ref/ReferenceQueue; 127 -Landroid/view/WindowManagerGlobal; 128 -Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool; 129 -Lcom/android/internal/util/function/pooled/PooledLambdaImpl;.sPool:Lcom/android/internal/util/function/pooled/PooledLambdaImpl$Pool;.mPool:[Ljava/lang/Object; 129 -Landroid/view/inputmethod/InputMethodManager; 130 -Landroid/media/MediaRouter; 131 -Landroid/hardware/SensorPrivacyManager; 132 -Landroid/os/storage/StorageManager; 133 -Landroid/view/contentcapture/ContentCaptureManager; 134 -Landroid/hardware/input/InputManager; 134 -Landroid/app/people/PeopleManager; 134 -Landroid/media/session/MediaSessionManager; 134 -Landroid/security/attestationverification/AttestationVerificationManager; 134 -Landroid/net/vcn/VcnManager; 134 -Landroid/os/RecoverySystem; 134 -Landroid/net/NetworkPolicyManager; 134 -Landroid/net/wifi/sharedconnectivity/app/SharedConnectivityManager; 134 -Landroid/permission/PermissionControllerManager; 134 -Landroid/app/tare/EconomyManager; 134 -Landroid/view/translation/TranslationManager; 134 -Landroid/view/textclassifier/TextClassificationManager; 134 -Landroid/view/autofill/AutofillManager; 134 -Landroid/os/SystemConfigManager; 134 -Landroid/view/LayoutInflater; 134 -Landroid/credentials/CredentialManager; 134 -Landroid/service/persistentdata/PersistentDataBlockManager; 134 -Landroid/view/textservice/TextServicesManager; 134 -Landroid/app/admin/DevicePolicyManager; 134 -Ljava/lang/StackStreamFactory; 134 -Landroid/view/WindowManager; 134 -Landroid/app/contentsuggestions/ContentSuggestionsManager; 134 -Landroid/media/tv/tunerresourcemanager/TunerResourceManager; 134 -Landroid/telephony/SubscriptionManager; 134 -Landroid/os/HardwarePropertiesManager; 134 -Landroid/media/AudioManager; 135 -Landroid/telephony/TelephonyManager; 136 -Landroid/util/ArrayMap; 137 -Landroid/app/QueuedWork; 138 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.0:Ljava/util/WeakHashMap$Entry; 139 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap; 140 -Ljava/util/concurrent/ScheduledThreadPoolExecutor;.sequencer:Ljava/util/concurrent/atomic/AtomicLong; 141 -Landroid/util/Log; 142 -Ljava/util/Collections$SynchronizedCollection; 143 -Ljava/util/Set; 143 -Ljava/util/Collections$SynchronizedSet; 143 -Ljava/util/Collection; 143 -Ljava/lang/Integer;.SMALL_NONNEG_VALUES:[Ljava/lang/String; 144 -Landroid/content/ComponentName; 145 -Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle; 146 -Landroid/os/PersistableBundle;.EMPTY:Landroid/os/PersistableBundle; 147 -Landroid/icu/impl/locale/BaseLocale;.CACHE:Landroid/icu/impl/locale/BaseLocale$Cache;._map:Ljava/util/concurrent/ConcurrentHashMap; 148 -Ljava/util/GregorianCalendar; 149 -Ljava/text/DontCareFieldPosition;.INSTANCE:Ljava/text/FieldPosition; 150 -Landroid/app/UiModeManager; 151 -Ljdk/internal/access/SharedSecrets; 152 -Landroid/icu/impl/ZoneMeta;.CANONICAL_ID_CACHE:Landroid/icu/impl/ICUCache; 153 -Landroid/icu/impl/ZoneMeta;.SYSTEM_ZONE_CACHE:Landroid/icu/impl/ZoneMeta$SystemTimeZoneCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 154 -Ljava/time/ZoneOffset;.ID_CACHE:Ljava/util/concurrent/ConcurrentMap; 155 -Ljava/time/ZoneOffset;.ID_CACHE:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 155 -Ljava/time/ZoneOffset;.SECONDS_CACHE:Ljava/util/concurrent/ConcurrentMap; 155 -Ljava/time/ZoneOffset;.SECONDS_CACHE:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.0:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 155 -Ljava/time/ZoneOffset;.SECONDS_CACHE:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 155 -Landroid/widget/TextView; 156 -Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool;.mPool:[Ljava/lang/Object; 157 -Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool; 157 -Landroid/view/ViewGroup; 158 -Landroid/graphics/Rect; 159 -Landroid/view/View$BaseSavedState; 160 -Landroid/widget/Button; 161 -Landroid/widget/ImageButton; 162 -Landroid/view/View$OnHoverListener; 163 -Landroid/widget/Toolbar; 164 -Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal; 165 -Landroid/app/WallpaperManager; 166 -Landroid/graphics/ColorSpace$Model;.RGB:Landroid/graphics/ColorSpace$Model; 166 -Landroid/graphics/drawable/AdaptiveIconDrawable; 167 -Landroid/animation/ValueAnimator$DurationScaleChangeListener; 168 -Landroid/widget/Toast; 168 -Landroid/app/smartspace/SmartspaceSession$OnTargetsAvailableListener; 168 -Landroid/view/CrossWindowBlurListeners; 168 -Landroid/app/servertransaction/ActivityRelaunchItem; 169 -[Ljava/util/concurrent/ForkJoinTask; 169 -Landroid/view/WindowManager$LayoutParams; 169 -Ljava/util/concurrent/ForkJoinPool$WorkQueue; 169 -Landroid/app/prediction/AppTargetEvent; 169 -Lorg/xmlpull/v1/XmlPullParserException; 169 -Landroid/app/servertransaction/ObjectPool;.sPoolMap:Ljava/util/Map; 170 -Landroid/app/servertransaction/ClientTransaction; 170 -Landroid/app/servertransaction/StopActivityItem; 170 -Landroid/system/ErrnoException; 171 -Landroid/hardware/location/ContextHubTransaction$OnCompleteListener; 172 -Landroid/app/PendingIntent$OnFinished; 172 -Ljava/lang/NullPointerException; 173 -Landroid/os/strictmode/DiskReadViolation; 174 -Lorg/apache/http/params/HttpParams; 175 -Landroid/nfc/cardemulation/CardEmulation; 176 -Ljava/io/FileDescriptor; 177 -Landroid/content/pm/PackageManager$OnPermissionsChangedListener; 178 -Landroid/security/keystore2/KeyStoreCryptoOperationUtils; 179 -Landroid/app/ActivityTaskManager; 180 -Landroid/util/EventLog; 181 -Ljava/net/URLConnection; 181 -Ljava/net/SocketException; 181 -Ljava/lang/reflect/InvocationTargetException; 181 -Ljava/lang/Enum; 182 -Landroid/widget/AbsListView$SelectionBoundsAdjuster; 183 -Ljava/lang/ClassNotFoundException; 183 -Landroid/content/SyncStatusObserver; 184 -Landroid/content/AsyncTaskLoader$LoadTask; 185 -Landroid/app/LoaderManager$LoaderCallbacks; 185 -Landroid/webkit/CookieSyncManager; 186 -Landroid/webkit/WebViewProvider$ViewDelegate; 187 -Landroid/webkit/WebView; 187 -Landroid/webkit/WebViewProvider$ScrollDelegate; 187 -Landroid/webkit/WebViewProvider; 187 -Landroid/webkit/WebViewFactory;.sTimestamps:Landroid/webkit/WebViewFactory$StartupTimestamps; 188 -Landroid/webkit/WebViewFactoryProvider; 189 -Landroid/webkit/WebViewFactory; 190 -Landroid/os/PowerManager$OnThermalStatusChangedListener; 191 -Landroid/os/Bundle; 192 -Landroid/widget/ProgressBar; 193 -Landroid/graphics/Bitmap$Config;.ALPHA_8:Landroid/graphics/Bitmap$Config; 194 -Landroid/graphics/Bitmap$Config;.RGB_565:Landroid/graphics/Bitmap$Config; 194 -Landroid/graphics/Bitmap$Config;.RGBA_1010102:Landroid/graphics/Bitmap$Config; 194 -Landroid/renderscript/Allocation;.mBitmapOptions:Landroid/graphics/BitmapFactory$Options;.inPreferredConfig:Landroid/graphics/Bitmap$Config; 194 -Landroid/graphics/Bitmap$Config;.RGBA_F16:Landroid/graphics/Bitmap$Config; 194 -Landroid/graphics/Bitmap$Config;.ARGB_4444:Landroid/graphics/Bitmap$Config; 194 -Landroid/graphics/Bitmap$Config;.HARDWARE:Landroid/graphics/Bitmap$Config; 194 -Landroid/graphics/drawable/StateListDrawable; 195 -Landroid/view/PointerIcon;.gSystemIconsByDisplay:Landroid/util/SparseArray; 196 -Landroid/view/PointerIcon; 196 -Ljavax/net/ssl/SSLServerSocketFactory; 197 -Ljavax/net/ssl/SSLSocketFactory; 198 -Ljavax/net/ssl/HttpsURLConnection$NoPreloadHolder; 198 -Ljavax/net/ssl/SSLSessionContext; 199 -Lcom/android/org/bouncycastle/crypto/CryptoServicesRegistrar; 200 -Lsun/security/x509/PKIXExtensions;.KeyUsage_Id:Lsun/security/util/ObjectIdentifier; 201 -Lsun/security/x509/PKIXExtensions;.PolicyConstraints_Id:Lsun/security/util/ObjectIdentifier; 201 -Ljava/security/cert/PKIXRevocationChecker$Option;.ONLY_END_ENTITY:Ljava/security/cert/PKIXRevocationChecker$Option; 201 -Lsun/security/x509/PKIXExtensions;.ExtendedKeyUsage_Id:Lsun/security/util/ObjectIdentifier; 201 -Lsun/security/provider/X509Factory;.certCache:Lsun/security/util/Cache; 201 -Lsun/security/x509/PKIXExtensions;.CertificatePolicies_Id:Lsun/security/util/ObjectIdentifier; 201 -Lsun/security/x509/PKIXExtensions;.NameConstraints_Id:Lsun/security/util/ObjectIdentifier; 201 -Lsun/security/x509/PKIXExtensions;.AuthorityKey_Id:Lsun/security/util/ObjectIdentifier; 201 -Lsun/security/provider/X509Factory;.certCache:Lsun/security/util/Cache;.cacheMap:Ljava/util/Map; 201 -Ljava/security/cert/PKIXRevocationChecker$Option;.NO_FALLBACK:Ljava/security/cert/PKIXRevocationChecker$Option; 201 -Lsun/security/x509/PKIXExtensions;.SubjectAlternativeName_Id:Lsun/security/util/ObjectIdentifier; 201 -Lsun/security/x509/PKIXExtensions;.PolicyMappings_Id:Lsun/security/util/ObjectIdentifier; 202 -Lsun/security/x509/PKIXExtensions;.InhibitAnyPolicy_Id:Lsun/security/util/ObjectIdentifier; 202 -Lsun/security/x509/PKIXExtensions;.BasicConstraints_Id:Lsun/security/util/ObjectIdentifier; 202 -Ljava/security/Security;.spiMap:Ljava/util/Map; 203 -Lsun/security/x509/X500Name;.commonName_oid:Lsun/security/util/ObjectIdentifier; 204 -Lsun/security/x509/X500Name;.countryName_oid:Lsun/security/util/ObjectIdentifier; 204 -Lsun/security/x509/X500Name;.orgName_oid:Lsun/security/util/ObjectIdentifier; 204 -Ljava/nio/charset/Charset;.cache2:Ljava/util/HashMap; 205 -Ljava/net/URL;.handlers:Ljava/util/Hashtable;.table:[Ljava/util/Hashtable$HashtableEntry; 206 -Ljava/net/URL;.handlers:Ljava/util/Hashtable; 206 -Ljava/net/Inet6AddressImpl;.addressCache:Ljava/net/AddressCache;.cache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap; 207 -Ljava/net/Proxy$Type;.DIRECT:Ljava/net/Proxy$Type; 208 -Ljava/net/ProxySelector;.theProxySelector:Ljava/net/ProxySelector; 208 -Lcom/android/okhttp/okio/SegmentPool; 209 -Lcom/android/okhttp/internal/http/AuthenticatorAdapter;.INSTANCE:Lcom/android/okhttp/Authenticator; 209 -Lcom/android/okhttp/HttpsHandler;.HTTP_1_1_ONLY:Ljava/util/List;.element:Ljava/lang/Object; 209 -Lcom/android/okhttp/ConfigAwareConnectionPool;.instance:Lcom/android/okhttp/ConfigAwareConnectionPool;.networkEventDispatcher:Llibcore/net/event/NetworkEventDispatcher;.listeners:Ljava/util/List; 210 -Lcom/android/okhttp/Dns;.SYSTEM:Lcom/android/okhttp/Dns; 210 -Lcom/android/okhttp/ConfigAwareConnectionPool;.instance:Lcom/android/okhttp/ConfigAwareConnectionPool; 210 -Lcom/android/okhttp/okio/AsyncTimeout; 211 -Ljava/lang/IllegalAccessException; 212 -Ljavax/net/ssl/SSLContext; 213 -Ljavax/net/ssl/HttpsURLConnection; 213 -Ljava/security/Security;.props:Ljava/util/Properties;.map:Ljava/util/concurrent/ConcurrentHashMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.12:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 214 -Ljava/security/Security;.props:Ljava/util/Properties;.map:Ljava/util/concurrent/ConcurrentHashMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.30:Ljava/util/concurrent/ConcurrentHashMap$Node; 214 -Landroid/database/sqlite/SQLiteTransactionListener; 215 -Landroid/accounts/OnAccountsUpdateListener; 216 -Landroid/accounts/AccountManager$20; 217 -Lsun/nio/ch/FileChannelImpl$Unmapper; 218 -Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.queue:Ljava/lang/ref/ReferenceQueue; 219 -Landroid/text/method/SingleLineTransformationMethod; 220 -Landroid/widget/RelativeLayout; 221 -Landroid/graphics/drawable/BitmapDrawable; 222 -Landroid/graphics/drawable/GradientDrawable; 223 -Landroid/animation/PropertyValuesHolder;.sGetterPropertyMap:Ljava/util/HashMap; 224 -Landroid/animation/PropertyValuesHolder$FloatPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 225 -Landroid/graphics/drawable/Drawable;.DEFAULT_TINT_MODE:Landroid/graphics/PorterDuff$Mode; 226 -Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 227 -Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 227 -Ljava/util/concurrent/ThreadLocalRandom; 228 -Landroid/widget/Space; 229 -Landroid/widget/ScrollView; 230 -Landroid/text/style/LineHeightSpan; 231 -Landroid/text/style/TabStopSpan; 232 -Landroid/text/style/ReplacementSpan; 233 -Landroid/text/style/MetricAffectingSpan; 233 -Landroid/text/style/LeadingMarginSpan; 233 -Landroid/text/style/LineBackgroundSpan; 234 -Landroid/text/style/CharacterStyle; 235 -Landroid/text/style/SuggestionSpan; 236 -Landroid/widget/TextView$ChangeWatcher; 237 -Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 238 -Landroid/text/DynamicLayout; 238 -Landroid/text/DynamicLayout$ChangeWatcher; 238 -Landroid/text/style/WrapTogetherSpan; 238 -Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 238 -Landroid/text/method/LinkMovementMethod; 239 -Landroid/text/style/ClickableSpan; 240 -Ljava/util/logging/LogRecord;.globalSequenceNumber:Ljava/util/concurrent/atomic/AtomicLong; 241 -Ljava/lang/Runtime;.currentRuntime:Ljava/lang/Runtime; 242 -Landroid/content/pm/LauncherActivityInfo; 243 -Landroid/database/sqlite/SQLiteMisuseException; 243 -Landroid/speech/tts/TextToSpeech$Connection$SetupConnectionAsyncTask; 243 -Landroid/database/sqlite/SQLiteCantOpenDatabaseException; 243 -Landroid/database/sqlite/SQLiteDatabaseCorruptException; 243 -Landroid/database/sqlite/SQLiteDatabaseLockedException; 243 -Ljava/util/Map$Entry; 243 -Ljava/util/zip/ZipException; 243 -Landroid/database/sqlite/SQLiteAccessPermException; 243 -Landroid/speech/tts/TextToSpeech$OnInitListener; 243 -Landroid/app/Notification$MessagingStyle; 244 -Landroid/text/TextUtils$TruncateAt; 245 -Landroid/app/smartspace/SmartspaceTarget; 246 -Landroid/app/prediction/AppTarget; 246 -Landroid/app/smartspace/uitemplatedata/BaseTemplateData; 246 -Landroid/location/LocationManager;.sLocationListeners:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 247 -Landroid/location/LocationManager;.sLocationListeners:Ljava/util/WeakHashMap; 247 -Landroid/service/notification/ConditionProviderService; 248 -Landroid/os/WorkSource; 249 -Landroid/security/keystore2/AndroidKeyStoreProvider; 249 -Ljava/net/Socket; 249 -Lcom/android/internal/listeners/ListenerTransport; 249 -Landroid/os/ParcelUuid; 250 -Landroid/telephony/emergency/EmergencyNumber; 251 -Lcom/android/internal/telephony/uicc/UiccProfile$4; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$IncomingSms; 251 -Lcom/android/internal/telephony/SmsStorageMonitor$1; 251 -Lcom/android/internal/telephony/TelephonyDevController; 251 -Lcom/android/internal/telephony/uicc/UiccController; 251 -Lcom/android/internal/telephony/emergency/EmergencyNumberTracker$1; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$DataCallSession; 251 -Lcom/android/internal/telephony/TelephonyDevController;.mSims:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsAcsProvisioningStats; 251 -Ljava/lang/UnsupportedOperationException; 251 -Landroid/database/CursorToBulkCursorAdaptor; 251 -Lcom/android/internal/telephony/satellite/PointingAppController; 251 -Landroid/telephony/ModemActivityInfo; 251 -Lcom/android/internal/telephony/imsphone/ImsPhone; 251 -Lcom/android/internal/telephony/ServiceStateTracker; 251 -Lcom/android/internal/telephony/IccSmsInterfaceManager; 251 -Lcom/android/internal/telephony/util/NotificationChannelController$1; 251 -Lcom/android/internal/telephony/RilWakelockInfo; 251 -Lcom/android/internal/telephony/gsm/GsmInboundSmsHandler$GsmCbTestBroadcastReceiver; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportFeatureTagStats; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$UceEventStats; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$BindingState; 251 -Lcom/android/internal/telephony/ims/ImsResolver$3; 251 -Landroid/net/NetworkPolicyManager$SubscriptionCallbackProxy; 251 -Lcom/android/internal/telephony/TelephonyDevController;.mModems:Ljava/util/ArrayList; 251 -Landroid/telephony/ims/aidl/IImsServiceController$Stub$Proxy; 251 -Lcom/android/internal/telephony/CarrierPrivilegesTracker$1; 251 -Lcom/android/internal/telephony/CommandException; 251 -Lcom/android/ims/FeatureConnector$1; 251 -Lcom/android/internal/telephony/IWapPushManager; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SipDelegateStats; 251 -Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mTtyModeReceivedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mMmiCompleteRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Lcom/android/i18n/timezone/TelephonyLookup; 251 -Landroid/telephony/BarringInfo$BarringServiceInfo; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequests; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$14; 251 -Lcom/android/internal/telephony/SmsBroadcastUndelivered; 251 -Lcom/android/internal/telephony/LocaleTracker; 251 -Lcom/android/internal/telephony/PhoneSubInfoController; 251 -Lcom/android/internal/telephony/CarrierKeyDownloadManager$1; 251 -Lcom/android/internal/telephony/GsmCdmaCallTracker$1; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.339:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/ServiceStateTracker$1; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.353:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/euicc/EuiccCardController$SimSlotStatusChangedBroadcastReceiver; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$PresenceNotifyEvent; 251 -Lcom/android/internal/telephony/SimActivationTracker$1; 251 -Landroid/telephony/ModemInfo; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1393:[Ljava/lang/String; 251 -Landroid/telephony/CellSignalStrengthWcdma; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteProvision; 251 -Lcom/android/internal/telephony/PhoneConfigurationManager; 251 -Lcom/android/internal/telephony/SmsApplication$SmsPackageMonitor; 251 -Landroid/telephony/TelephonyRegistryManager$3; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallSession; 251 -Landroid/os/Handler$MessengerImpl; 251 -Lcom/android/internal/telephony/LocaleTracker$1; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularDataServiceSwitch; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOffRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSosMessageRecommender; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationServiceDescStats; 251 -Lcom/android/internal/telephony/uicc/UiccPkcs15$Pkcs15Selector; 251 -Lcom/android/internal/telephony/CarrierResolver$2; 251 -Lcom/android/internal/telephony/CarrierActionAgent$1; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mPhones:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/SmsController; 251 -Lcom/android/internal/telephony/uicc/euicc/EuiccCardException; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationTermination; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1125:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/NetworkTypeController$1; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.803:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/uicc/asn1/TagNotFoundException; 251 -Lcom/android/internal/telephony/CarrierServiceBindHelper$1; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularServiceState; 251 -Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager; 251 -Lcom/android/internal/telephony/InboundSmsHandler$NewMessageNotificationActionReceiver; 251 -Lcom/android/internal/telephony/CarrierActionAgent; 251 -Lcom/android/i18n/timezone/TimeZoneFinder; 251 -Lcom/android/internal/telephony/RILRequest; 251 -Lcom/android/internal/telephony/RIL;.sRilTimeHistograms:Landroid/util/SparseArray; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.33:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/MccTable; 251 -Lcom/android/internal/telephony/uicc/UiccProfile$2; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierIdMismatch; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1235:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/uicc/UiccCarrierPrivilegeRules; 251 -Landroid/telephony/CellSignalStrengthTdscdma; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerListenerEvent; 251 -Lcom/android/internal/telephony/SmsDispatchersController; 251 -Landroid/timezone/TelephonyLookup; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteOutgoingDatagram; 251 -Lcom/android/internal/telephony/SMSDispatcher$1; 251 -Lcom/android/internal/telephony/AppSmsManager; 251 -Landroid/timezone/TimeZoneFinder; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mBackgroundCalls:Ljava/util/ArrayList; 251 -Lcom/android/ims/rcs/uce/eab/EabProvider; 251 -Lcom/android/internal/telephony/uicc/PinStorage$1; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerEvent; 251 -Landroid/telephony/CellSignalStrengthLte; 251 -Landroid/telephony/ims/ProvisioningManager$Callback$CallbackBinder; 251 -Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mKeys:[I 251 -Landroid/telephony/CellSignalStrengthNr; 251 -Lcom/android/internal/telephony/SomeArgs; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOnRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/StateMachine$SmHandler; 251 -Lcom/android/internal/telephony/PackageChangeReceiver; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingShortCodeSms; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequestsV2; 251 -Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$AvailableState; 251 -Lcom/android/ims/internal/IImsServiceFeatureCallback$Stub$Proxy; 251 -Landroid/telephony/data/ApnSetting;.APN_TYPE_INT_MAP:Ljava/util/Map; 251 -Lcom/android/internal/telephony/RadioInterfaceCapabilityController; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationStats; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallRatUsage; 251 -Lcom/android/internal/telephony/metrics/TelephonyMetrics; 251 -Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall; 251 -Lcom/android/internal/telephony/NetworkRegistrationManager$NetworkRegStateCallback; 251 -Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState; 251 -Lcom/android/internal/telephony/RadioConfig; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$DisconnectedState; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSession; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisplayInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo; 251 -Lcom/android/internal/telephony/GsmCdmaPhone; 251 -Lcom/android/internal/telephony/TelephonyTester$1; 251 -Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaScpTestBroadcastReceiver; 251 -Lcom/android/internal/telephony/NetworkTypeController$DefaultState; 251 -Landroid/net/TelephonyNetworkSpecifier; 251 -Lcom/android/internal/telephony/NitzStateMachine; 251 -Landroid/app/timezonedetector/TimeZoneDetector; 251 -Lcom/android/internal/telephony/IntentBroadcaster$1; 251 -Lcom/android/internal/telephony/uicc/UiccStateChangedLauncher; 251 -Lcom/android/internal/telephony/ims/ImsResolver$1; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingSms; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$UnmeteredNetworks; 251 -Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map; 251 -Lcom/android/internal/telephony/euicc/EuiccController; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mForegroundCalls:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/satellite/SatelliteModemInterface; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.531:[Ljava/lang/String; 251 -Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$1; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.467:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SipMessageResponse; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportSession; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$UnavailableState; 251 -Lcom/android/internal/telephony/DeviceStateMonitor$3; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSignalInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 251 -Lcom/android/internal/telephony/IccPhoneBookInterfaceManager; 251 -Lcom/android/internal/telephony/DisplayInfoController; 251 -Lcom/android/internal/telephony/ims/ImsResolver$2; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1377:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyServiceState$NetworkRegistrationInfo; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteController; 251 -Landroid/telephony/ims/RegistrationManager$RegistrationCallback$RegistrationBinder; 251 -Lcom/android/internal/telephony/imsphone/ImsExternalCallTracker; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$EmergencyNumbersInfo; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$GbaEvent; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.23:[Ljava/lang/String; 251 -Landroid/telephony/CellSignalStrengthCdma; 251 -Landroid/telephony/TelephonyLocalConnection; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteIncomingDatagram; 251 -Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker$2; 251 -Lcom/android/internal/telephony/ims/ImsResolver; 251 -Lcom/android/internal/telephony/SmsStorageMonitor; 251 -Lcom/android/internal/telephony/uicc/UiccProfile; 251 -Landroid/telephony/ims/ImsMmTelManager$CapabilityCallback$CapabilityBinder; 251 -Lcom/android/internal/telephony/euicc/EuiccCardController; 251 -Lcom/android/internal/telephony/SmsBroadcastUndelivered$1; 251 -Lcom/android/internal/telephony/GsmCdmaCallTracker; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsClientProvisioningStats; 251 -Lcom/android/internal/telephony/cat/CatService; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.761:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/SmsApplication; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisconnectRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/PhoneFactory; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mHandlerMap:Ljava/util/HashMap; 251 -Landroid/os/AsyncResult; 251 -Lcom/android/internal/telephony/ProxyController; 251 -Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaCbTestBroadcastReceiver; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.453:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/MultiSimSettingController; 251 -Ljava/io/BufferedReader; 251 -Landroid/telephony/CellSignalStrengthGsm; 251 -Lcom/android/internal/telephony/SimActivationTracker; 251 -Lcom/android/internal/telephony/CellBroadcastServiceManager; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mRingingCalls:Ljava/util/ArrayList; 251 -Lcom/android/internal/telephony/IntentBroadcaster; 251 -Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationFeatureTagStats; 251 -Lcom/android/internal/telephony/euicc/EuiccConnector$EuiccPackageMonitor; 251 -Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSuppServiceFailedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 251 -Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node; 251 -Lcom/android/internal/telephony/CarrierServiceBindHelper$CarrierServicePackageMonitor; 251 -Lcom/android/internal/telephony/TelephonyComponentFactory; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.637:[Ljava/lang/String; 251 -Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo; 251 -Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.651:[Ljava/lang/String; 251 -Lcom/android/internal/telephony/SmsUsageMonitor; 251 -Lcom/android/internal/telephony/CommandException$Error;.INVALID_SIM_STATE:Lcom/android/internal/telephony/CommandException$Error; 251 -Landroid/hardware/display/DisplayManagerGlobal$DisplayManagerCallback; 252 -Landroid/app/ActivityThread$ApplicationThread; 252 -Landroid/app/ActivityManager$MyUidObserver; 253 -Landroid/media/browse/MediaBrowser$ServiceCallbacks; 253 -Landroid/media/session/MediaController$CallbackStub; 253 -Landroid/media/session/MediaSessionManager$OnMediaKeyEventSessionChangedListener; 253 -Landroid/app/PendingIntent$CancelListener; 253 -Landroid/media/AudioManager$2; 253 -Landroid/database/ContentObserver$Transport; 253 -Landroid/media/session/MediaSessionManager$SessionsChangedWrapper$1; 253 -Landroid/content/ContentProvider$PipeDataWriter; 254 -Landroid/security/net/config/UserCertificateSource$NoPreloadHolder; 255 -Landroid/view/Window$Callback; 256 -Landroid/transition/TransitionManager;.sDefaultTransition:Landroid/transition/Transition;.mTransitions:Ljava/util/ArrayList;.elementData:[Ljava/lang/Object;.1:Landroid/transition/ChangeBounds;.mCurrentAnimators:Ljava/util/ArrayList; 256 -Landroid/transition/TransitionManager;.sDefaultTransition:Landroid/transition/Transition;.mTransitions:Ljava/util/ArrayList;.elementData:[Ljava/lang/Object;.2:Landroid/transition/Fade;.mCurrentAnimators:Ljava/util/ArrayList; 256 -Landroid/transition/TransitionManager;.sPendingTransitions:Ljava/util/ArrayList; 256 -Landroid/transition/TransitionManager;.sDefaultTransition:Landroid/transition/Transition;.mTransitions:Ljava/util/ArrayList;.elementData:[Ljava/lang/Object;.0:Landroid/transition/Fade;.mCurrentAnimators:Ljava/util/ArrayList; 256 -Landroid/view/AttachedSurfaceControl$OnBufferTransformHintChangedListener; 257 -Landroid/webkit/ValueCallback; 258 -Landroid/webkit/WebResourceRequest; 258 -Landroid/webkit/WebChromeClient$CustomViewCallback; 258 -Landroid/hardware/camera2/CameraCharacteristics;.INFO_SUPPORTED_HARDWARE_LEVEL:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 258 -Landroid/accounts/Account; 258 -Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 258 -Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/params/StreamConfigurationDuration; 259 -Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/params/HighSpeedVideoConfiguration; 259 -Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -I 259 -Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_CAPABILITIES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 259 -Landroid/hardware/camera2/params/StreamConfiguration; 259 -Z 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PIXEL_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.LENS_FOCAL_LENGTH:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_APERTURES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.DISTORTION_CORRECTION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.EXTENSION_STRENGTH:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -J 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PHYSICAL_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.TONEMAP_PRESET_CURVE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -B 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_TRIGGER:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_EXPOSURE_COMPENSATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -[D 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_REGIONS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_SESSION_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SENSOR_EXPOSURE_TIME:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_CALIBRATION_TRANSFORM2:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_TRANSFORM:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/content/res/Resources$Theme; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_LOCK:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -F 260 -Landroid/hardware/camera2/CaptureRequest;.LENS_FILTER_DENSITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.LENS_OPTICAL_STABILIZATION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.NOISE_REDUCTION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SENSOR_FRAME_DURATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_REGIONS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_EXTENDED_SCENE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.STATISTICS_OIS_DATA_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SENSOR_TEST_PATTERN_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.HOT_PIXEL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_ANTIBANDING_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.STATISTICS_LENS_SHADING_MAP_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SCALER_CROP_REGION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.LENS_FOCUS_DISTANCE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.TONEMAP_GAMMA:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_REFERENCE_ILLUMINANT2:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_REFERENCE_ILLUMINANT1:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -[F 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_ZOOM_RATIO:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_ABERRATION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.TONEMAP_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_REQUEST_KEYS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SCALER_ROTATE_AND_CROP:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_GAINS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_COLOR_TRANSFORM1:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SENSOR_SENSITIVITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_FOCAL_LENGTHS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_OPTICAL_BLACK_REGIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.JPEG_QUALITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.FLASH_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_POST_RAW_SENSITIVITY_BOOST:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_WHITE_LEVEL:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_SETTINGS_OVERRIDE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -[Landroid/hardware/camera2/params/MeteringRectangle; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.LOGICAL_MULTI_CAMERA_PHYSICAL_IDS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_EXPOSURE_TIME_RANGE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Ljava/lang/Float; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_ENABLE_ZSL:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.INFO_DEVICE_STATE_ORIENTATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_CALIBRATION_TRANSFORM1:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.EDGE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_CAPTURE_INTENT:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_ORIENTATION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.JPEG_ORIENTATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_COLOR_TRANSFORM2:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -[J 260 -Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Ljava/util/concurrent/Phaser; 260 -Landroid/hardware/camera2/CaptureRequest;.BLACK_LEVEL_LOCK:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.COLOR_CORRECTION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_SCENE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.JPEG_THUMBNAIL_SIZE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SHADING_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.STATISTICS_FACE_DETECT_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.STATISTICS_HOT_PIXEL_MAP_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AUTOFRAMING:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_TARGET_FPS_RANGE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_LOCK:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.SENSOR_TEST_PATTERN_DATA:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_PRECAPTURE_TRIGGER:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.FLASH_STRENGTH_LEVEL:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_VIDEO_STABILIZATION_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Ljava/lang/Boolean; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_EFFECT_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.LENS_APERTURE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.JPEG_THUMBNAIL_QUALITY:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_REGIONS:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -Ljava/lang/Long; 260 -Landroid/hardware/camera2/CaptureRequest;.SENSOR_PIXEL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 260 -[Ljava/lang/String; 261 -[Z 262 -Ljava/lang/Class$Caches;.genericInterfaces:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap; 263 -Ljava/util/Map; 264 -Ljava/nio/Bits; 265 -Ljava/nio/DirectByteBuffer; 266 -Ljava/io/File; 267 -Ljava/nio/ByteBuffer; 268 -Ljava/io/InputStream; 269 -Landroid/os/ParcelFileDescriptor; 270 -Landroid/os/BinderProxy;.sProxyMap:Landroid/os/BinderProxy$ProxyMap; 271 -Landroid/app/PendingIntent; 272 -Landroid/content/Intent; 273 -Landroid/net/Uri$HierarchicalUri; 274 -Landroid/net/Uri$StringUri; 275 -Landroid/net/Uri$PathPart;.EMPTY:Landroid/net/Uri$PathPart; 276 -Lcom/android/internal/telephony/MccTable;.FALLBACKS:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 277 -Landroid/icu/text/DecimalFormatSymbols;.cachedLocaleData:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 278 -Llibcore/icu/DecimalFormatData;.CACHE:Ljava/util/concurrent/ConcurrentHashMap; 279 -Landroid/icu/impl/CurrencyData;.provider:Landroid/icu/impl/CurrencyData$CurrencyDisplayInfoProvider; 280 -Lcom/android/internal/infra/AndroidFuture; 281 -Lcom/android/internal/util/LatencyTracker$Action; 282 -Landroid/app/AppOpsManager$Mode; 283 -Landroid/view/accessibility/AccessibilityManager$AccessibilityServicesStateChangeListener; 284 -Landroid/annotation/IdRes; 285 -Landroid/content/pm/PackageItemInfo; 286 -Ljava/util/Random; 287 -Landroid/widget/RadioButton; 288 -Lcom/android/internal/policy/PhoneWindow$PanelFeatureState$SavedState; 289 -Landroid/graphics/Insets; 290 -Landroid/view/View;.sNextGeneratedId:Ljava/util/concurrent/atomic/AtomicInteger; 291 -Landroid/graphics/drawable/LayerDrawable; 292 -Landroid/animation/LayoutTransition; 293 -Llibcore/reflect/AnnotationFactory;.cache:Ljava/util/Map; 294 -Llibcore/reflect/AnnotationFactory;.cache:Ljava/util/Map;.table:[Ljava/util/WeakHashMap$Entry; 294 -Ljava/lang/reflect/Proxy;.proxyClassCache:Ljava/lang/reflect/WeakCache;.reverseMap:Ljava/util/concurrent/ConcurrentMap; 295 -Ljava/lang/reflect/Proxy$ProxyClassFactory;.nextUniqueNumber:Ljava/util/concurrent/atomic/AtomicLong; 295 -Ljava/lang/reflect/Proxy;.proxyClassCache:Ljava/lang/reflect/WeakCache;.map:Ljava/util/concurrent/ConcurrentMap; 296 -Ljava/lang/Object; 297 -Ljava/lang/invoke/MethodType;.internTable:Ljava/lang/invoke/MethodType$ConcurrentWeakInternSet;.map:Ljava/util/concurrent/ConcurrentMap; 298 -Ljava/nio/channels/SocketChannel;.dexCache:Ljava/lang/Object; 298 -Ljava/lang/invoke/MethodType;.objectOnlyTypes:[Ljava/lang/invoke/MethodType; 299 -Ljava/util/concurrent/ForkJoinTask; 300 -Ljava/util/concurrent/CompletableFuture; 301 -Landroid/app/Notification$BigTextStyle; 302 -Landroid/content/pm/ApplicationInfo; 303 -Ljava/security/Signature;.signatureInfo:Ljava/util/Map;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.13:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 304 -Lsun/security/x509/X500Name;.stateName_oid:Lsun/security/util/ObjectIdentifier; 305 -Lsun/security/x509/X500Name;.localityName_oid:Lsun/security/util/ObjectIdentifier; 306 -Lsun/security/x509/X500Name;.orgUnitName_oid:Lsun/security/util/ObjectIdentifier; 306 -Ljava/util/UUID; 307 -Landroid/app/slice/Slice; 308 -Ljava/util/Locale;.FRENCH:Ljava/util/Locale; 308 -Landroid/os/NullVibrator; 308 -Ldalvik/system/CloseGuard;.MESSAGE:Ljava/lang/String; 308 -Lsun/util/locale/BaseLocale$Cache;.CACHE:Lsun/util/locale/BaseLocale$Cache;.map:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.22:Ljava/util/concurrent/ConcurrentHashMap$Node;.val:Ljava/lang/Object;.referent:Ljava/lang/Object; 308 -Ljava/util/Locale$Cache;.LOCALECACHE:Ljava/util/Locale$Cache;.map:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.24:Ljava/util/concurrent/ConcurrentHashMap$Node;.val:Ljava/lang/Object;.referent:Ljava/lang/Object; 308 -Landroid/app/Activity$$ExternalSyntheticLambda0; 308 -Landroid/icu/impl/locale/BaseLocale;.CACHE:Landroid/icu/impl/locale/BaseLocale$Cache;._map:Ljava/util/concurrent/ConcurrentHashMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.0:Ljava/util/concurrent/ConcurrentHashMap$Node; 308 -Ljava/util/Locale;.ITALIAN:Ljava/util/Locale; 308 -Landroid/media/MediaRouter2Manager$Callback; 308 -Lsun/util/locale/BaseLocale$Cache;.CACHE:Lsun/util/locale/BaseLocale$Cache;.map:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.29:Ljava/util/concurrent/ConcurrentHashMap$Node;.val:Ljava/lang/Object;.referent:Ljava/lang/Object; 308 -Ljava/util/Locale;.GERMAN:Ljava/util/Locale; 309 -Landroid/icu/impl/StandardPlural; 310 -Landroid/icu/impl/number/range/StandardPluralRanges; 311 -Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader; 311 -Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader;.pluralRulesCache:Ljava/util/Map; 311 -Landroid/icu/text/PluralRules$Operand; 311 -Landroid/icu/util/Calendar;.PATTERN_CACHE:Landroid/icu/impl/ICUCache; 312 -Landroid/icu/impl/DateNumberFormat;.CACHE:Landroid/icu/impl/SimpleCache; 313 -Landroid/text/format/DateFormat; 314 -Landroid/view/View$OnDragListener; 315 -Landroid/hardware/input/InputManager$InputDeviceListener; 316 -Landroid/hardware/input/InputManagerGlobal; 317 -Landroid/hardware/SystemSensorManager; 318 -Lcom/android/internal/os/BackgroundThread; 319 -Ljava/lang/Throwable; 320 -Landroid/app/NotificationManager; 321 -Landroid/app/NotificationChannel; 322 -Landroid/content/SharedPreferences$OnSharedPreferenceChangeListener; 323 -Landroid/content/pm/VersionedPackage; 324 -Landroid/app/AppOpsManager; 325 -Ldalvik/system/ZipPathValidator; 326 -Landroid/content/pm/PackageManager;.sPackageInfoCache:Landroid/app/PropertyInvalidatedCache;.mSkips:[J 327 -Landroid/content/pm/PackageManager;.sApplicationInfoCache:Landroid/app/PropertyInvalidatedCache;.mSkips:[J 328 -Lsun/util/locale/BaseLocale$Cache;.CACHE:Lsun/util/locale/BaseLocale$Cache;.map:Ljava/util/concurrent/ConcurrentMap; 329 -Landroid/content/Context; 330 -Ljava/util/concurrent/Executor; 331 -Ljava/util/concurrent/ScheduledExecutorService; 332 -Ljava/util/concurrent/ExecutorService; 332 -Landroid/view/Window$OnFrameMetricsAvailableListener; 333 -Ljava/lang/annotation/Annotation; 334 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 335 -Ljava/util/concurrent/CancellationException; 336 -Ljava/lang/NoSuchMethodException; 337 -Landroid/os/strictmode/CustomViolation; 338 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.3:Ljava/util/WeakHashMap$Entry; 339 -Lcom/android/internal/policy/PhoneWindow; 340 -Landroid/view/autofill/AutofillValue; 340 -Landroid/widget/TextView$SavedState; 341 -Landroid/text/method/MetaKeyKeyListener;.SYM:Ljava/lang/Object; 342 -Landroid/text/method/MetaKeyKeyListener;.ALT:Ljava/lang/Object; 342 -Landroid/text/method/MetaKeyKeyListener;.SELECTING:Ljava/lang/Object; 342 -Landroid/text/method/MetaKeyKeyListener;.CAP:Ljava/lang/Object; 342 -Landroid/widget/PopupWindow$PopupBackgroundView; 343 -Landroid/widget/TextView;.TEMP_RECTF:Landroid/graphics/RectF; 343 -Landroid/text/method/ScrollingMovementMethod; 343 -Landroid/icu/impl/locale/UnicodeLocaleExtension;.EMPTY_SORTED_SET:Ljava/util/SortedSet;.m:Ljava/util/NavigableMap; 343 -Landroid/widget/PopupWindow$PopupDecorView; 343 -Landroid/widget/Editor$TextRenderNode; 343 -Landroid/widget/Editor$PositionListener; 344 -Landroid/text/style/SpellCheckSpan; 345 -Landroid/text/method/ArrowKeyMovementMethod; 346 -Landroid/text/method/TextKeyListener;.sInstance:[Landroid/text/method/TextKeyListener; 346 -Landroid/text/TextUtils$TruncateAt;.MARQUEE:Landroid/text/TextUtils$TruncateAt; 347 -Landroid/view/autofill/Helper; 348 -Lcom/android/internal/util/LatencyTracker; 349 -Lcom/android/internal/util/LatencyTracker$SLatencyTrackerHolder; 349 -Landroid/graphics/drawable/Icon; 350 -Landroid/text/style/AlignmentSpan; 351 -Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 352 -Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool; 352 -Landroid/icu/impl/ICUResourceBundleReader;.CACHE:Landroid/icu/impl/ICUResourceBundleReader$ReaderCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 353 -Landroid/location/ILocationManager$Stub;.dexCache:Ljava/lang/Object; 354 -Landroid/annotation/CurrentTimeMillisLong; 355 -Ljava/lang/reflect/Method; 356 -Lcom/android/internal/os/ZygoteInit; 356 -Landroid/database/DatabaseUtils; 356 -Landroid/os/HandlerThread; 356 -Ljava/security/Signature;.signatureInfo:Ljava/util/Map;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 357 -Ljava/security/Signature;.signatureInfo:Ljava/util/Map; 358 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.queue:Ljava/lang/ref/ReferenceQueue; 359 -Landroid/telephony/TelephonyRegistryManager; 360 -Landroid/graphics/HardwareRenderer; 361 -Landroid/os/BinderProxy; 362 -Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache;.mCache:Ljava/util/LinkedHashMap; 363 -Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache; 363 -Landroid/app/AlarmManager; 364 -Landroid/net/metrics/DhcpClientEvent; 365 -[I 366 -Landroid/media/MediaCodecList; 367 -Landroid/graphics/drawable/InsetDrawable; 368 -Landroid/widget/ProgressBar$SavedState; 369 -Landroid/widget/ScrollView$SavedState; 370 -Landroid/graphics/drawable/AnimatedVectorDrawable; 371 -Landroid/widget/ListView; 372 -Landroid/widget/AbsListView; 373 -Landroid/widget/AbsListView$SavedState; 373 -Landroid/widget/CompoundButton$SavedState; 374 -Landroid/widget/HorizontalScrollView$SavedState; 375 -Landroid/widget/HorizontalScrollView; 376 -Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.1:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 377 -Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 377 -Landroid/view/View$OnSystemUiVisibilityChangeListener; 378 -Ljava/util/AbstractMap; 379 -Landroid/telephony/euicc/EuiccCardManager$ResultCallback; 380 -Ljava/lang/Character$UnicodeBlock;.CJK_SYMBOLS_AND_PUNCTUATION:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.KANBUN:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.HANGUL_COMPATIBILITY_JAMO:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.KATAKANA:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.HANGUL_SYLLABLES:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.ENCLOSED_CJK_LETTERS_AND_MONTHS:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.HANGUL_JAMO:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.BOPOMOFO_EXTENDED:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY_FORMS:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.BOPOMOFO:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.HIRAGANA:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.HALFWIDTH_AND_FULLWIDTH_FORMS:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.KANGXI_RADICALS:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.CJK_RADICALS_SUPPLEMENT:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.KATAKANA_PHONETIC_EXTENSIONS:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY:Ljava/lang/Character$UnicodeBlock; 381 -Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT:Ljava/lang/Character$UnicodeBlock; 382 -Ljava/lang/Character$UnicodeBlock;.CJK_COMPATIBILITY_IDEOGRAPHS:Ljava/lang/Character$UnicodeBlock; 382 -Ljava/lang/Character$UnicodeBlock;.CJK_UNIFIED_IDEOGRAPHS:Ljava/lang/Character$UnicodeBlock; 382 -Ljava/lang/Character$UnicodeBlock;.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A:Ljava/lang/Character$UnicodeBlock; 382 -Ljava/lang/Character$UnicodeBlock;.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B:Ljava/lang/Character$UnicodeBlock; 382 -Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry; 383 -Landroid/view/Window$DecorCallback; 383 -Landroid/view/inputmethod/EditorInfo; 383 -Landroid/view/MenuItem$OnActionExpandListener; 384 -Ljava/util/Locale;.JAPANESE:Ljava/util/Locale; 385 -Ljava/util/Locale;.KOREAN:Ljava/util/Locale; 385 -Lcom/android/internal/config/appcloning/AppCloningDeviceConfigHelper; 386 -Landroid/telecom/PhoneAccountHandle; 387 -Landroid/content/AsyncQueryHandler; 388 -Landroid/speech/RecognitionListener; 389 -Ljava/lang/InstantiationException; 390 -Ljava/util/concurrent/ExecutionException; 391 -Landroid/icu/text/DateIntervalInfo;.DIICACHE:Landroid/icu/impl/ICUCache; 392 -Landroid/text/format/DateIntervalFormat;.CACHED_FORMATTERS:Landroid/util/LruCache;.map:Ljava/util/LinkedHashMap; 392 -Landroid/icu/text/DateIntervalFormat;.LOCAL_PATTERN_CACHE:Landroid/icu/impl/ICUCache; 392 -Landroid/icu/impl/OlsonTimeZone; 392 -Landroid/text/format/DateIntervalFormat;.CACHED_FORMATTERS:Landroid/util/LruCache; 392 -Landroid/graphics/drawable/Drawable; 393 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 394 -Landroid/app/Activity; 395 -Landroid/icu/text/PluralRules$KeywordStatus;.INVALID:Landroid/icu/text/PluralRules$KeywordStatus;.name:Ljava/lang/String; 396 -Landroid/net/Uri; 396 -Lsun/util/calendar/CalendarSystem;.calendars:Ljava/util/concurrent/ConcurrentMap; 396 -Landroid/animation/PropertyValuesHolder$IntPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 397 -Landroid/graphics/drawable/ShapeDrawable; 398 -Lcom/android/internal/widget/ActionBarContextView; 399 -Landroid/widget/Toolbar$SavedState; 399 -Lcom/android/internal/widget/ActionBarContainer; 399 -Lcom/android/internal/widget/ActionBarOverlayLayout; 399 -Lcom/android/internal/widget/ActionBarContainer$ActionBarBackgroundDrawable; 399 -Landroid/widget/ActionMenuPresenter$OverflowMenuButton; 400 -Landroid/widget/ActionMenuView; 401 -Landroid/content/res/Configuration; 402 -Ljava/util/IdentityHashMap;.NULL_KEY:Ljava/lang/Object; 403 -Ljava/util/concurrent/ForkJoinPool; 404 -Landroid/os/ResultReceiver; 405 -Ljava/util/concurrent/TimeoutException; 406 -Ljava/io/IOException; 407 -Landroid/accounts/AccountAuthenticatorResponse; 408 -Landroid/nfc/NfcAdapter; 409 -Landroid/nfc/NfcAdapter;.sNfcAdapters:Ljava/util/HashMap; 409 -Landroid/app/backup/BackupManager; 410 -Landroid/app/NotificationChannelGroup; 411 -Landroid/content/pm/ParceledListSlice; 411 -Landroid/os/FileObserver; 412 -Landroid/os/UserHandle; 413 -Landroid/content/pm/PackageManager$NameNotFoundException; 414 -[Ljava/lang/Integer; 415 -Landroid/animation/PropertyValuesHolder;.sSetterPropertyMap:Ljava/util/HashMap; 415 -Landroid/content/LocusId; 416 -Landroid/view/contentcapture/ContentCaptureContext; 416 -Landroid/telephony/ims/RegistrationManager;.IMS_REG_TO_ACCESS_TYPE_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.18:Ljava/lang/Integer; 417 -Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.4:Ljava/util/concurrent/ConcurrentHashMap$Node; 418 -Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node; 418 -Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.2:Ljava/util/concurrent/ConcurrentHashMap$Node;.next:Ljava/util/concurrent/ConcurrentHashMap$Node; 418 -Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.9:Ljava/lang/Integer; 418 -Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap;.table:[Ljava/util/concurrent/ConcurrentHashMap$Node;.56:Ljava/util/concurrent/ConcurrentHashMap$Node; 418 -Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.5:Ljava/lang/Integer; 418 -Lcom/android/org/bouncycastle/asn1/ASN1ObjectIdentifier;.pool:Ljava/util/concurrent/ConcurrentMap; 418 -Landroid/widget/EditText; 419 -Landroid/widget/CheckedTextView; 420 -Landroid/os/strictmode/UnsafeIntentLaunchViolation; 421 -Landroid/app/Service; 422 -Ldalvik/system/BlockGuard; 423 -Landroid/hardware/devicestate/DeviceStateManagerGlobal; 424 -Landroid/hardware/camera2/CameraCharacteristics;.SCALER_MULTI_RESOLUTION_STREAM_SUPPORTED:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 425 -Landroid/hardware/camera2/marshal/MarshalRegistry;.sMarshalerMap:Ljava/util/HashMap; 425 -Landroid/hardware/camera2/CameraCharacteristics;.LENS_FACING:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 425 -Landroid/content/ClipboardManager$OnPrimaryClipChangedListener; 426 -Landroid/icu/text/BreakIterator;.iterCache:[Landroid/icu/impl/CacheValue; 427 -Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.13:Ljava/util/WeakHashMap$Entry; 428 -Landroid/icu/text/Collator; 429 -Landroid/icu/impl/number/parse/NanMatcher;.DEFAULT:Landroid/icu/impl/number/parse/NanMatcher;.uniSet:Landroid/icu/text/UnicodeSet;.strings:Ljava/util/SortedSet;.c:Ljava/util/Collection;.m:Ljava/util/NavigableMap; 430 -Ljava/io/FileNotFoundException; 431 -Landroid/os/BaseBundle; 432 -Landroid/service/watchdog/ExplicitHealthCheckService$PackageConfig; 433 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.116:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.12:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.90:Ljava/lang/String; 434 -Landroid/icu/text/MessageFormat;.rootLocale:Ljava/util/Locale;.baseLocale:Lsun/util/locale/BaseLocale;.language:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.385:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.107:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.112:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.480:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.550:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.143:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._obsoleteLanguages:[Ljava/lang/String;.1:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.473:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.138:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.204:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.71:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.12:Ljava/lang/String; 434 -Landroid/icu/impl/duration/impl/DataRecord$ETimeLimit;.names:[Ljava/lang/String;.1:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.9:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.99:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages3:[Ljava/lang/String;.152:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.256:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.170:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.220:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.461:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._replacementLanguages:[Ljava/lang/String;.5:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.190:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.157:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._obsoleteCountries:[Ljava/lang/String;.4:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.196:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.117:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.5:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.499:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.199:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.18:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.324:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.101:Ljava/lang/String; 434 -Landroid/icu/impl/locale/UnicodeLocaleExtension;.CA_JAPANESE:Landroid/icu/impl/locale/UnicodeLocaleExtension;._keywords:Ljava/util/SortedMap;.root:Ljava/util/TreeMap$TreeMapEntry;.key:Ljava/lang/Object; 434 -Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.3:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.140:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.105:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.37:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.5:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.22:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.103:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.412:Ljava/lang/String; 434 -Landroid/icu/impl/duration/impl/DataRecord$EMilliSupport;.names:[Ljava/lang/String;.1:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.124:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.232:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.219:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.179:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.523:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.75:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.486:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.166:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.112:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.119:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.160:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.298:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.257:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.182:Ljava/lang/String; 434 -Landroid/icu/impl/units/UnitPreferences;.measurementSystem:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.5:Ljava/util/HashMap$Node;.next:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.47:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.180:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.111:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.358:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.96:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._obsoleteLanguages:[Ljava/lang/String;.0:Ljava/lang/String; 434 -Landroid/icu/text/DateFormat;.HOUR_GENERIC_TZ:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.67:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.254:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.222:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.55:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.349:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.16:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.352:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.443:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.478:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.19:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.401:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.137:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.65:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.474:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.168:Ljava/lang/String; 434 -Landroid/icu/impl/units/UnitPreferences;.measurementSystem:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.15:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.111:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages3:[Ljava/lang/String;.545:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.30:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.469:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.21:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.69:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.56:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.519:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._replacementLanguages:[Ljava/lang/String;.4:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.107:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.290:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.59:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.220:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.186:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.516:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.181:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.199:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.396:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.117:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.227:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.331:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.447:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.151:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.144:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.132:Ljava/lang/String; 434 -Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.230:Ljava/lang/String; 434 -Landroid/icu/text/DateFormat;.MINUTE_SECOND:Ljava/lang/String; 434 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 435 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.head:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 436 -Ljava/lang/Enum;.sharedConstantsCache:Llibcore/util/BasicLruCache;.map:Ljava/util/LinkedHashMap;.tail:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry;.before:Ljava/util/LinkedHashMap$LinkedHashMapEntry; 437 -Landroid/graphics/drawable/ColorStateListDrawable; 438 -Ljava/lang/SecurityException; 439 -Ljava/lang/RuntimeException; 440 -Landroid/media/audiopolicy/AudioProductStrategy; 441 -Landroid/os/PersistableBundle; 442 -Landroid/content/pm/ShortcutInfo; 442 -Landroid/icu/text/TimeZoneFormat;._tzfCache:Landroid/icu/text/TimeZoneFormat$TimeZoneFormatCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 443 -Landroid/graphics/LeakyTypefaceStorage;.sStorage:Ljava/util/ArrayList; 443 -Landroid/graphics/LeakyTypefaceStorage;.sTypefaceMap:Landroid/util/ArrayMap; 443 -Landroid/text/TextWatcher; 444 -Landroid/view/accessibility/AccessibilityManager$TouchExplorationStateChangeListener; 445 -Ljavax/net/SocketFactory; 446 -Ljava/util/Collections; 447 -Ljava/lang/Exception; 448 -Landroid/os/UserManager; 449 -Landroid/os/RemoteException; 450 -Landroid/content/AttributionSource; 451 -Lcom/android/okhttp/internalandroidapi/HttpURLConnectionFactory$DnsAdapter; 452 -Lcom/android/okhttp/Protocol;.HTTP_2:Lcom/android/okhttp/Protocol; 452 -Ljava/net/Inet4Address; 452 -Lcom/android/okhttp/Protocol;.SPDY_3:Lcom/android/okhttp/Protocol; 452 -Lcom/android/okhttp/OkHttpClient; 452 -Landroid/os/storage/VolumeInfo; 453 -Landroid/os/storage/DiskInfo; 453 -Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mMap:Landroid/util/ArrayMap; 454 -Ljava/nio/file/StandardOpenOption;.WRITE:Ljava/nio/file/StandardOpenOption; 455 -Ljava/nio/file/StandardOpenOption;.APPEND:Ljava/nio/file/StandardOpenOption; 456 -Ljava/util/logging/FileHandler; 457 -Ljava/nio/file/StandardOpenOption;.CREATE_NEW:Ljava/nio/file/StandardOpenOption; 457 -Ljava/util/logging/FileHandler;.locks:Ljava/util/Set;.map:Ljava/util/HashMap; 457 -Lsun/nio/ch/SharedFileLockTable;.queue:Ljava/lang/ref/ReferenceQueue; 458 -Ljavax/net/ServerSocketFactory; 458 -Landroid/os/AsyncTask; 459 -Landroid/os/strictmode/UnbufferedIoViolation; 460 -Landroid/app/usage/AppStandbyInfo; 461 -Landroid/text/format/DateUtils; 462 -Landroid/security/IKeyChainService; 463 -Landroid/util/Log$TerribleFailure; 464 -Lcom/android/internal/os/RuntimeInit$KillApplicationHandler; 464 -Ljava/util/Timer;.nextSerialNumber:Ljava/util/concurrent/atomic/AtomicInteger; 465 -Landroid/telephony/ims/stub/ImsConfigImplBase$ImsConfigStub; 466 -Landroid/telephony/ims/stub/ImsRegistrationImplBase$1; 466 -Landroid/telephony/ims/ImsUtListener; 466 -Landroid/telephony/ims/feature/MmTelFeature$1; 466 -Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 467 -Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray; 467 -Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mKeys:[I 467 -Landroid/telephony/ims/aidl/IImsConfig$Stub$Proxy; 468 -Landroid/telephony/ims/aidl/IImsRegistration$Stub$Proxy; 468 -Landroid/telephony/NetworkService; 469 -Landroid/telephony/TelephonyCallback$ServiceStateListener; 470 -Landroid/telephony/TelephonyCallback$PhysicalChannelConfigListener; 471 -Landroid/telephony/TelephonyCallback$RadioPowerStateListener; 471 -Lsun/security/x509/X500Name;.internedOIDs:Ljava/util/Map; 472 -Lsun/security/x509/X500Name;.internedOIDs:Ljava/util/Map;.table:[Ljava/util/HashMap$Node; 472 -Landroid/media/MediaCodec; 473 -Ljava/nio/file/StandardOpenOption;.CREATE:Ljava/nio/file/StandardOpenOption; 474 -Ljava/nio/file/NoSuchFileException; 475 -Ljava/text/DateFormatSymbols;.cachedInstances:Ljava/util/concurrent/ConcurrentMap; 476 -Ljava/util/Currency;.instances:Ljava/util/concurrent/ConcurrentMap; 476 -Ljava/util/Calendar;.cachedLocaleData:Ljava/util/concurrent/ConcurrentMap; 476 -Ljava/text/SimpleDateFormat;.cachedNumberFormatData:Ljava/util/concurrent/ConcurrentMap; 476 -Landroid/app/UriGrantsManager;.IUriGrantsManagerSingleton:Landroid/util/Singleton; 477 -Landroid/content/ContentProviderProxy; 478 -Landroid/os/DeadObjectException; 479 -Landroid/app/slice/SliceSpec; 479 -Landroid/database/sqlite/SQLiteDatabase; 480 -Ljava/util/Locale;.CHINA:Ljava/util/Locale; 481 -Ljava/util/Locale;.TAIWAN:Ljava/util/Locale; 481 -Ljava/util/Locale;.KOREA:Ljava/util/Locale; 481 -Ljava/util/Scanner; 482 -Ljava/math/BigDecimal; 483 -Ljava/security/interfaces/RSAPrivateCrtKey; 483 -Ljava/security/interfaces/RSAPrivateKey; 483 -Lcom/android/server/backup/AccountSyncSettingsBackupHelper;.KEY_ACCOUNT_TYPE:Ljava/lang/String; 483 -Landroid/util/UtilConfig; 484 -Ljava/net/ResponseCache; 485 -Landroid/content/ReceiverCallNotAllowedException; 486 -Landroid/app/ReceiverRestrictedContext; 487 -Landroid/os/strictmode/CredentialProtectedWhileLockedViolation; 488 -Landroid/app/Application; 489 -Ljava/util/NoSuchElementException; 490 -Landroid/os/Messenger; 491 -Landroid/telephony/TelephonyCallback$DataEnabledListener; 491 -Landroid/system/StructLinger; 492 diff --git a/config/dirty-image-objects.txt b/config/dirty-image-objects.txt new file mode 100644 index 000000000000..2c51511305ae --- /dev/null +++ b/config/dirty-image-objects.txt @@ -0,0 +1,1391 @@ +# +# 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. +# +# +# +# Framework dirty-image-objects file for boot image. +# The image writer will bin these objects together in the image. +# There are two dirty-image-objects files: +# 1) art/build/apex/dirty-image-objects.txt - contains classes and objects +# referenced by classes defined in the ART module. +# 2) frameworks/base/config/dirty-image-objects.txt - contains classes and +# objects referenced by classes defined in framework modules. +# +# More info about dirty objects format and how to collect the data can be +# found in: art/imgdiag/dirty_image_objects.md +# This particular file was generated by running top 100 Android apps. +# +Landroid/util/Base64; 0 +Landroid/os/Debug; 1 +Landroid/content/pm/ResolveInfo; 4 +Landroid/widget/AutoCompleteTextView; 7 +Landroid/widget/ToggleButton; 8 +Landroid/widget/MultiAutoCompleteTextView; 9 +Landroid/speech/RecognitionListener; 10 +Landroid/icu/util/Calendar;.PATTERN_CACHE:Landroid/icu/impl/ICUCache; 12 +Landroid/icu/impl/DateNumberFormat;.CACHE:Landroid/icu/impl/SimpleCache; 13 +Landroid/view/ViewTreeObserver$OnWindowVisibilityChangeListener; 14 +Landroid/window/WindowContainerTransaction$Change; 14 +Landroid/window/WindowOrganizer;.IWindowOrganizerControllerSingleton:Landroid/util/Singleton; 14 +Landroid/widget/Toast; 14 +Landroid/app/smartspace/SmartspaceSession$OnTargetsAvailableListener; 14 +Landroid/view/CrossWindowBlurListeners; 15 +Landroid/text/TextUtils$TruncateAt; 16 +Landroid/app/smartspace/SmartspaceTarget; 17 +Landroid/app/smartspace/uitemplatedata/BaseTemplateData; 17 +Landroid/content/res/Resources$NotFoundException; 18 +Landroid/app/prediction/AppTarget; 18 +Landroid/icu/impl/number/parse/NanMatcher;.DEFAULT:Landroid/icu/impl/number/parse/NanMatcher;.uniSet:Landroid/icu/text/UnicodeSet;.strings:Ljava/util/SortedSet;.c:Ljava/util/Collection;.m:Ljava/util/NavigableMap; 19 +Landroid/app/prediction/AppTargetEvent; 19 +Landroid/content/res/Configuration; 19 +Landroid/os/strictmode/UnbufferedIoViolation; 21 +Lcom/android/internal/os/ProcessCpuTracker$FilterStats; 22 +Lcom/android/internal/view/WindowManagerPolicyThread; 22 +Landroid/content/pm/ShortcutServiceInternal; 22 +Landroid/app/ServiceStartArgs; 22 +Lcom/android/internal/util/ToBooleanFunction; 22 +Landroid/app/ActivityManager$RecentTaskInfo; 22 +Landroid/telecom/Logging/EventManager$EventListener; 22 +Landroid/app/assist/AssistStructure; 22 +Landroid/app/Notification$DecoratedMediaCustomViewStyle; 22 +Landroid/annotation/StringRes; 22 +Landroid/util/MemoryIntArray; 22 +Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 22 +Landroid/view/RoundedCorners; 22 +Landroid/media/AudioSystem$DynamicPolicyCallback; 22 +Lcom/android/internal/R$styleable;.Window:[I 22 +Landroid/app/servertransaction/PauseActivityItem; 22 +Landroid/content/pm/PermissionGroupInfo; 22 +Landroid/app/job/JobInfo; 22 +Lcom/android/server/criticalevents/nano/CriticalEventLogProto; 22 +Lcom/android/internal/infra/ServiceConnector$Impl$CompletionAwareJob; 22 +Landroid/net/metrics/NetworkEvent; 22 +Landroid/hardware/camera2/CameraManager$CameraManagerGlobal; 22 +Landroid/debug/AdbManagerInternal; 22 +Landroid/app/ActivityManagerInternal; 22 +Landroid/hardware/soundtrigger/SoundTrigger$StatusListener; 22 +Landroid/hardware/display/DisplayManagerInternal; 22 +Landroid/hardware/location/IActivityRecognitionHardwareClient; 22 +Landroid/hardware/sidekick/SidekickInternal; 22 +Landroid/media/AudioSystem; 22 +Landroid/net/Uri$PathPart;.NULL:Landroid/net/Uri$PathPart; 22 +Lcom/android/internal/os/LongArrayMultiStateCounter; 22 +Landroid/hardware/location/NanoAppMessage; 22 +Lcom/android/internal/widget/LockSettingsInternal; 22 +Landroid/accounts/AuthenticatorException; 22 +Landroid/window/DisplayAreaAppearedInfo; 22 +Landroid/content/pm/RegisteredServicesCache$2; 22 +Landroid/permission/PermissionManagerInternal; 22 +Landroid/content/pm/CrossProfileAppsInternal; 22 +Landroid/app/assist/ActivityId; 22 +Landroid/os/Process;.ZYGOTE_PROCESS:Landroid/os/ZygoteProcess; 22 +Landroid/appwidget/AppWidgetManagerInternal; 22 +Landroid/net/metrics/ValidationProbeEvent; 22 +Landroid/webkit/WebViewLibraryLoader$RelroFileCreator; 22 +Landroid/graphics/GraphicsStatsService; 22 +Lcom/android/internal/os/LongArrayMultiStateCounter$LongArrayContainer; 22 +Landroid/content/pm/PackageManager$Property; 22 +Landroid/app/servertransaction/DestroyActivityItem; 22 +Landroid/os/PowerManagerInternal; 22 +Landroid/app/SystemServiceRegistry; 22 +Landroid/hardware/location/GeofenceHardwareImpl; 22 +Landroid/app/AppOpsManager$NoteOpEvent; 22 +Lcom/android/framework/protobuf/nano/MessageNano; 22 +Landroid/content/pm/RegisteredServicesCache$3; 22 +Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker; 22 +Landroid/accounts/AccountManagerInternal; 22 +Landroid/app/PropertyInvalidatedCache;.sCorkedInvalidates:Ljava/util/HashMap; 22 +Landroid/media/audiopolicy/AudioVolumeGroup; 22 +Landroid/app/admin/DevicePolicyManagerInternal$OnCrossProfileWidgetProvidersChangeListener; 22 +Landroid/content/pm/dex/ArtManagerInternal; 22 +Landroid/app/UriGrantsManager;.IUriGrantsManagerSingleton:Landroid/util/Singleton; 22 +Lcom/android/internal/os/RuntimeInit$ApplicationWtfHandler; 22 +Lcom/android/internal/app/procstats/AssociationState;.sTmpSourceKey:Lcom/android/internal/app/procstats/AssociationState$SourceKey; 22 +Landroid/app/servertransaction/StopActivityItem; 22 +Landroid/hardware/soundtrigger/SoundTriggerModule$EventHandlerDelegate; 22 +Landroid/app/PendingIntent$FinishedDispatcher; 22 +Landroid/app/servertransaction/NewIntentItem; 22 +Landroid/content/pm/UserPackage; 22 +Landroid/net/metrics/DhcpErrorEvent; 22 +Lcom/android/internal/listeners/ListenerExecutor$FailureCallback; 22 +Landroid/app/time/TimeZoneConfiguration; 22 +Landroid/net/metrics/IpManagerEvent; 22 +Landroid/view/Display$HdrCapabilities; 22 +Landroid/media/AudioSystem$AudioRecordingCallback; 22 +Landroid/telecom/PhoneAccountHandle; 22 +Lcom/android/server/criticalevents/nano/CriticalEventProto$InstallPackages; 22 +Landroid/os/BatteryManagerInternal; 22 +Landroid/provider/Settings; 22 +Lcom/android/internal/util/function/LongObjPredicate; 22 +Landroid/view/WindowManagerPolicyConstants; 22 +Landroid/accessibilityservice/AccessibilityServiceInfo; 22 +Lcom/android/internal/os/CachedDeviceState$Readonly; 22 +Landroid/service/voice/VoiceInteractionManagerInternal; 22 +Landroid/service/notification/Condition; 22 +Landroid/util/EventLog; 22 +Lcom/android/server/AppWidgetBackupBridge; 22 +Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventCallback; 22 +Lcom/android/internal/os/StatsdHiddenApiUsageLogger;.sInstance:Lcom/android/internal/os/StatsdHiddenApiUsageLogger; 22 +Landroid/app/AppOpsManager$AttributedOpEntry; 22 +Landroid/attention/AttentionManagerInternal; 22 +Landroid/os/PatternMatcher;.sParsedPatternScratch:[I 22 +Landroid/view/DisplayAddress$Physical; 22 +Lcom/android/internal/util/function/DodecConsumer; 22 +Landroid/app/servertransaction/StartActivityItem; 22 +Landroid/window/IOnBackInvokedCallback$Stub$Proxy; 22 +Landroid/content/ComponentName$WithComponentName; 22 +Landroid/media/AudioPlaybackConfiguration$PlayerDeathMonitor; 22 +Landroid/telecom/Log; 22 +Landroid/view/ViewDebug$ExportedProperty; 22 +Landroid/graphics/Region;.sPool:Landroid/util/Pools$SynchronizedPool; 22 +Lcom/android/internal/os/LongMultiStateCounter; 22 +Landroid/content/pm/parsing/ApkLite; 22 +Landroid/app/PropertyInvalidatedCache;.sCorks:Ljava/util/HashMap; 22 +Landroid/content/pm/PackageInstaller$SessionInfo; 22 +Landroid/os/RemoteCallback$1; 22 +Landroid/os/UEventObserver; 22 +Landroid/app/AppOpsManagerInternal; 22 +Lcom/android/internal/infra/AbstractRemoteService$VultureCallback; 22 +Landroid/media/AudioSystem$ErrorCallback; 22 +Landroid/window/TaskAppearedInfo; 22 +Landroid/content/pm/ShortcutServiceInternal$ShortcutChangeListener; 22 +Landroid/service/notification/NotificationListenerService$RankingMap; 22 +Landroid/app/PropertyInvalidatedCache$AutoCorker$1; 22 +Landroid/view/autofill/AutofillManagerInternal; 22 +Lcom/android/internal/content/om/OverlayConfig$PackageProvider; 22 +Lcom/android/internal/os/BatteryStatsHistory$HistoryStepDetailsCalculator; 22 +Lcom/android/internal/os/KernelCpuBpfTracking; 22 +Landroid/os/ServiceSpecificException; 22 +Landroid/content/pm/RegisteredServicesCache$1; 22 +Landroid/app/time/TimeZoneCapabilities; 22 +Landroid/content/res/ResourceTimer; 22 +Landroid/app/servertransaction/ConfigurationChangeItem; 22 +Lcom/android/internal/infra/AndroidFuture$1; 22 +Landroid/content/pm/PackageManager;.sCacheAutoCorker:Landroid/app/PropertyInvalidatedCache$AutoCorker;.mLock:Ljava/lang/Object; 22 +Lcom/android/internal/content/om/OverlayConfig; 22 +Landroid/app/ActivityManager; 22 +Landroid/net/metrics/ApfProgramEvent; 22 +Landroid/graphics/Bitmap$CompressFormat; 22 +Landroid/window/ITaskOrganizer$Stub$Proxy; 22 +Landroid/app/servertransaction/ActivityResultItem; 22 +Landroid/service/notification/ConditionProviderService; 22 +Landroid/hardware/display/DeviceProductInfo; 22 +Landroid/hardware/display/DeviceProductInfo$ManufactureDate; 22 +Landroid/app/ApplicationExitInfo; 22 +Landroid/app/admin/DevicePolicyManagerInternal; 22 +Lcom/android/server/usage/AppStandbyInternal; 22 +Landroid/app/PropertyInvalidatedCache;.sCorkLock:Ljava/lang/Object; 22 +Lcom/android/internal/display/BrightnessSynchronizer; 22 +Landroid/service/notification/StatusBarNotification; 22 +Landroid/os/FileUtils$ProgressListener; 22 +Landroid/media/AudioAttributes; 22 +Landroid/service/autofill/FillContext; 22 +Landroid/hardware/biometrics/BiometricSourceType; 22 +Landroid/hardware/location/GeofenceHardwareService; 22 +Landroid/app/servertransaction/ActivityConfigurationChangeItem; 22 +Landroid/content/pm/LauncherActivityInfoInternal; 22 +Landroid/telecom/Logging/SessionManager$ISessionListener; 22 +Landroid/text/format/TimeFormatter; 22 +Landroid/hardware/location/ContextHubInfo; 22 +Landroid/os/ServiceManager$ServiceNotFoundException; 22 +Landroid/hardware/biometrics/ComponentInfoInternal; 22 +Lcom/android/internal/os/LongArrayMultiStateCounter;.sTmpArrayContainer:Ljava/util/concurrent/atomic/AtomicReference; 22 +Lcom/android/internal/config/appcloning/AppCloningDeviceConfigHelper; 22 +Landroid/os/storage/StorageVolume; 22 +Landroid/app/AppOpsManager$SamplingStrategy; 22 +Lcom/android/server/LocalServices;.sLocalServiceObjects:Landroid/util/ArrayMap; 22 +Landroid/media/AudioPlaybackConfiguration; 22 +Landroid/view/ViewDebug$FlagToString; 22 +Landroid/service/dreams/DreamManagerInternal; 22 +Lcom/android/server/criticalevents/nano/CriticalEventProto$NativeCrash; 22 +Landroid/net/wifi/nl80211/WifiNl80211Manager$ScanEventHandler; 22 +Landroid/util/ArrayMap;.sBaseCacheLock:Ljava/lang/Object; 22 +Landroid/content/pm/FallbackCategoryProvider;.sFallbacks:Landroid/util/ArrayMap; 22 +Landroid/os/SharedMemory; 22 +Landroid/media/AudioManagerInternal$RingerModeDelegate; 22 +Landroid/util/NtpTrustedTime; 22 +Landroid/appwidget/AppWidgetProviderInfo; 22 +Landroid/telephony/ServiceState; 22 +Landroid/service/notification/ZenPolicy; 22 +Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation; 22 +Landroid/content/pm/UserPackage;.sCacheLock:Ljava/lang/Object; 22 +Lcom/android/server/WidgetBackupProvider; 22 +Lcom/android/internal/os/LooperStats; 22 +Lcom/android/internal/infra/AbstractRemoteService$AsyncRequest; 22 +Landroid/content/pm/BaseParceledListSlice$1; 22 +Landroid/media/AudioManagerInternal; 22 +Landroid/content/pm/UserPackage;.sCache:Landroid/util/SparseArrayMap;.mData:Landroid/util/SparseArray; 22 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mLock:Ljava/lang/Object; 22 +Landroid/content/pm/RegisteredServicesCacheListener; 22 +Landroid/view/WindowManagerPolicyConstants$PointerEventListener; 22 +Landroid/accounts/AccountManagerInternal$OnAppPermissionChangeListener; 22 +Landroid/database/ContentObserver$$ExternalSyntheticLambda1; 22 +Landroid/webkit/WebViewZygote; 22 +Landroid/content/pm/LauncherApps$ShortcutQuery$QueryFlags; 22 +Lcom/android/server/criticalevents/nano/CriticalEventProto$SystemServerStarted; 22 +Landroid/os/storage/StorageManagerInternal; 22 +Landroid/service/notification/ZenModeConfig; 22 +Landroid/icu/text/MessagePattern;.argTypes:[Landroid/icu/text/MessagePattern$ArgType;.0:Landroid/icu/text/MessagePattern$ArgType;.name:Ljava/lang/String; 27 +Lcom/android/internal/app/procstats/DumpUtils;.STATE_NAMES_CSV:[Ljava/lang/String;.12:Ljava/lang/String; 27 +Landroid/media/MediaDrm; 27 +Landroid/icu/util/GenderInfo$Gender;.OTHER:Landroid/icu/util/GenderInfo$Gender;.name:Ljava/lang/String; 27 +Landroid/provider/DocumentsContract;.DOWNLOADS_PROVIDER_AUTHORITY:Ljava/lang/String; 27 +Landroid/annotation/SystemApi; 27 +Landroid/icu/util/MeasureUnit$Complexity;.MIXED:Landroid/icu/util/MeasureUnit$Complexity;.name:Ljava/lang/String; 27 +Landroid/webkit/WebViewFactory;.sTimestamps:Landroid/webkit/WebViewFactory$StartupTimestamps; 55 +Landroid/webkit/WebViewFactoryProvider; 56 +Landroid/webkit/WebViewFactory; 57 +Landroid/webkit/WebViewProvider$ScrollDelegate; 58 +Landroid/webkit/WebViewProvider; 58 +Landroid/webkit/WebViewProvider$ViewDelegate; 58 +Landroid/webkit/CookieSyncManager; 59 +Landroid/view/PointerIcon;.SYSTEM_ICONS:Landroid/util/SparseArray; 60 +Landroid/webkit/WebView; 61 +Landroid/webkit/WebResourceRequest; 62 +Landroid/webkit/DownloadListener; 63 +Landroid/webkit/WebViewFactoryProvider$Statics; 64 +Landroid/window/WindowTokenClientController; 65 +Landroid/os/PowerManager$OnThermalStatusChangedListener; 66 +Landroid/content/ClipboardManager$OnPrimaryClipChangedListener; 67 +Landroid/hardware/input/InputManager$InputDeviceListener; 68 +Landroid/hardware/input/InputManagerGlobal; 69 +Landroid/media/MediaCodecInfo$CodecCapabilities$FeatureList; 70 +Landroid/media/MediaCodecList; 70 +Landroid/media/MediaCodec; 71 +Landroid/os/ParcelFileDescriptor; 72 +Landroid/webkit/ValueCallback; 73 +Landroid/view/View$OnDragListener; 74 +Landroid/view/autofill/Helper; 75 +Landroid/app/ActivityTaskManager;.IActivityTaskManagerSingleton:Landroid/util/Singleton; 78 +Lcom/android/internal/inputmethod/ImeTracing; 79 +Landroid/view/SurfaceControlRegistry; 79 +Landroid/view/inputmethod/IInputMethodManagerGlobalInvoker; 80 +Landroid/window/BackTouchTracker; 80 +Landroid/view/Choreographer; 81 +Lcom/android/internal/policy/DecorView; 82 +Landroid/window/SurfaceSyncGroup; 82 +Landroid/view/ViewTreeObserver; 82 +Landroid/view/accessibility/AccessibilityNodeIdManager; 82 +Landroid/view/ViewRootImpl; 82 +Landroid/widget/FrameLayout; 82 +Landroid/view/ViewStub; 82 +Landroid/os/SystemProperties;.sChangeCallbacks:Ljava/util/ArrayList; 83 +Lcom/android/internal/os/SomeArgs; 84 +Landroid/view/autofill/AutofillId; 85 +Landroid/app/ActivityClient;.sInstance:Landroid/util/Singleton; 86 +Landroid/app/ActivityClient;.INTERFACE_SINGLETON:Landroid/app/ActivityClient$ActivityClientControllerSingleton; 86 +Landroid/os/StrictMode$InstanceTracker;.sInstanceCounts:Ljava/util/HashMap; 87 +Landroid/widget/LinearLayout; 88 +Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray; 89 +Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mKeys:[I 89 +Landroid/view/ViewConfiguration;.sConfigurations:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 89 +Landroid/view/accessibility/AccessibilityManager; 90 +Landroid/graphics/Bitmap; 91 +Landroid/content/SharedPreferences; 113 +Landroid/app/AppOpsManager; 115 +Landroid/os/Parcel;.mCreators:Ljava/util/HashMap; 117 +Landroid/os/GraphicsEnvironment;.sInstance:Landroid/os/GraphicsEnvironment; 117 +Landroid/os/Process; 117 +Landroid/os/Parcel;.sPairedCreators:Ljava/util/HashMap; 117 +Landroid/os/Parcel; 117 +Landroid/app/ApplicationLoaders;.gApplicationLoaders:Landroid/app/ApplicationLoaders;.mLoaders:Landroid/util/ArrayMap; 117 +Landroid/graphics/Typeface; 118 +Landroid/provider/DeviceConfigInitializer; 118 +Landroid/icu/util/TimeZone; 118 +Landroid/view/View; 118 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap; 118 +Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap; 118 +Landroid/app/ActivityThread; 118 +Landroid/util/ArraySet; 118 +Landroid/hardware/display/DisplayManagerGlobal; 118 +Landroid/os/LocaleList; 118 +Landroid/telephony/TelephonyFrameworkInitializer; 118 +Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 118 +Landroid/os/Binder; 118 +Landroid/os/DdmSyncState; 118 +Landroid/media/MediaFrameworkPlatformInitializer; 118 +Lcom/android/internal/os/RuntimeInit; 118 +Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.table:[Ljava/util/WeakHashMap$Entry; 118 +Landroid/os/Message; 118 +Landroid/security/net/config/SystemCertificateSource$NoPreloadHolder; 118 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 118 +Landroid/app/LoadedApk;.sApplications:Landroid/util/ArrayMap;.mHashes:[I 118 +Landroid/ddm/DdmHandleAppName; 118 +Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map; 118 +Landroid/os/ServiceManager; 118 +Landroid/os/Looper; 118 +Landroid/se/omapi/SeFrameworkInitializer; 118 +Landroid/os/StrictMode; 118 +Landroid/os/Environment; 118 +Landroid/security/net/config/ApplicationConfig; 118 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 119 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 120 +Landroid/app/servertransaction/ClientTransactionListenerController; 121 +Lcom/android/internal/os/BinderInternal; 122 +Landroid/graphics/Compatibility; 123 +Landroid/renderscript/RenderScriptCacheDir; 123 +Landroid/app/DexLoadReporter;.INSTANCE:Landroid/app/DexLoadReporter;.mDataDirs:Ljava/util/Set;.map:Ljava/util/HashMap; 123 +Landroid/graphics/Canvas; 123 +Landroid/provider/FontsContract; 123 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 126 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 127 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 128 +Landroid/graphics/HardwareRenderer; 129 +Landroid/app/QueuedWork; 130 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry; 131 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.10:Ljava/util/WeakHashMap$Entry; 132 +Landroid/content/Context; 133 +Landroid/view/inputmethod/InputMethodManager; 134 +Landroid/view/WindowManagerGlobal; 135 +Landroid/telephony/TelephonyManager; 136 +Landroid/database/CursorWindow; 137 +Landroid/database/sqlite/SQLiteGlobal; 138 +Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder; 138 +Landroid/database/sqlite/SQLiteCompatibilityWalFlags; 138 +Landroid/content/ContentResolver; 139 +Landroid/app/NotificationManager; 140 +Landroid/os/Handler; 141 +Landroid/icu/impl/locale/BaseLocale;.CACHE:Landroid/icu/impl/locale/BaseLocale$Cache;._map:Ljava/util/concurrent/ConcurrentHashMap; 142 +Landroid/graphics/Bitmap;.sAllBitmaps:Ljava/util/WeakHashMap; 143 +Landroid/text/TextUtils; 144 +Landroid/graphics/drawable/ColorDrawable; 145 +Landroid/transition/ChangeImageTransform; 146 +Landroid/transition/ChangeTransform; 146 +Landroid/transition/ChangeClipBounds; 146 +Landroid/text/TextLine;.sCached:[Landroid/text/TextLine; 147 +Landroid/graphics/TemporaryBuffer; 148 +Landroid/text/Layout;.sTempRect:Landroid/graphics/Rect; 149 +Landroid/content/res/ColorStateList;.sCache:Landroid/util/SparseArray; 150 +Landroid/widget/ImageView; 151 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object; 163 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mCache:Ljava/util/LinkedHashMap; 164 +Landroid/content/pm/VersionedPackage; 171 +Landroid/content/SharedPreferences$OnSharedPreferenceChangeListener; 172 +Landroid/database/sqlite/SQLiteDatabase$CursorFactory; 173 +Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle; 174 +Landroid/content/ComponentName; 175 +Landroid/icu/impl/ZoneMeta;.SYSTEM_ZONE_CACHE:Landroid/icu/impl/ZoneMeta$SystemTimeZoneCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 180 +Landroid/icu/impl/ZoneMeta;.CANONICAL_ID_CACHE:Landroid/icu/impl/ICUCache; 181 +Landroid/icu/impl/ICUResourceBundleReader;.CACHE:Landroid/icu/impl/ICUResourceBundleReader$ReaderCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 182 +Landroid/app/UiModeManager; 183 +Landroid/media/AudioManager; 184 +Landroid/util/ArrayMap; 185 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 188 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 189 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 190 +Landroid/icu/text/DecimalFormatSymbols;.cachedLocaleData:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 205 +Landroid/icu/impl/CurrencyData;.provider:Landroid/icu/impl/CurrencyData$CurrencyDisplayInfoProvider; 206 +Landroid/icu/text/DateFormatSymbols;.DFSCACHE:Landroid/icu/impl/CacheBase;.map:Ljava/util/concurrent/ConcurrentHashMap; 211 +Landroid/animation/AnimatorInflater;.sTmpTypedValue:Landroid/util/TypedValue; 217 +Landroid/graphics/drawable/RippleDrawable; 218 +Landroid/text/method/SingleLineTransformationMethod; 219 +Landroid/text/style/SuggestionSpan; 220 +Landroid/text/SpannableStringBuilder;.sCachedIntBuffer:[[I 221 +Landroid/widget/TextView$ChangeWatcher; 222 +Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 223 +Landroid/text/DynamicLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 223 +Landroid/text/DynamicLayout; 223 +Landroid/text/DynamicLayout$ChangeWatcher; 224 +Landroid/text/style/WrapTogetherSpan; 225 +Landroid/text/Selection$MemoryTextWatcher; 226 +Landroid/text/Selection;.SELECTION_MEMORY:Ljava/lang/Object; 227 +Landroid/text/Selection;.SELECTION_END:Ljava/lang/Object; 227 +Landroid/text/Selection;.SELECTION_START:Ljava/lang/Object; 227 +Landroid/text/method/TextKeyListener;.sInstance:[Landroid/text/method/TextKeyListener; 228 +Landroid/text/method/ArrowKeyMovementMethod; 228 +Landroid/view/textclassifier/TextClassificationConstants; 229 +Landroid/text/style/SpellCheckSpan; 230 +Landroid/text/TextUtils$TruncateAt;.MARQUEE:Landroid/text/TextUtils$TruncateAt; 231 +Landroid/text/style/ReplacementSpan; 232 +Landroid/text/style/LineBackgroundSpan; 233 +Landroid/text/style/LeadingMarginSpan; 234 +Landroid/text/style/TabStopSpan; 235 +Landroid/text/style/LineBreakConfigSpan; 236 +Landroid/text/style/MetricAffectingSpan; 237 +Landroid/text/style/CharacterStyle; 238 +Landroid/text/style/AlignmentSpan; 239 +Landroid/text/SpanWatcher; 240 +Lcom/android/internal/util/ArrayUtils;.sCache:[Ljava/lang/Object; 241 +Landroid/text/TextWatcher; 242 +Landroid/text/style/ClickableSpan; 243 +Landroid/text/style/LineHeightSpan; 244 +Landroid/text/method/LinkMovementMethod; 245 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.queue:Ljava/lang/ref/ReferenceQueue; 246 +Landroid/view/ViewRootImpl$$ExternalSyntheticLambda11; 247 +Landroid/content/res/Resources;.sResourcesHistory:Ljava/util/Set;.c:Ljava/util/Collection;.m:Ljava/util/Map;.queue:Ljava/lang/ref/ReferenceQueue; 249 +Landroid/app/NotificationChannel; 251 +Landroid/os/HandlerThread; 252 +Lcom/android/internal/os/ZygoteInit; 252 +Landroid/database/DatabaseUtils; 252 +Landroid/annotation/CurrentTimeMillisLong; 253 +Landroid/os/AsyncTask; 254 +Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 255 +Landroid/text/StaticLayout$Builder;.sPool:Landroid/util/Pools$SynchronizedPool; 255 +Landroid/graphics/drawable/GradientDrawable; 256 +Landroid/graphics/drawable/StateListDrawable; 257 +Landroid/widget/RelativeLayout; 258 +Landroid/graphics/drawable/BitmapDrawable; 259 +Landroid/graphics/drawable/Drawable; 262 +Landroid/view/TextureView$SurfaceTextureListener; 265 +Landroid/graphics/SurfaceTexture; 266 +Landroid/media/audiopolicy/AudioProductStrategy; 267 +Landroid/media/PlayerBase; 268 +Landroid/os/FileUtils; 269 +Landroid/app/AppOpsManager$OnOpActiveChangedListener; 270 +Landroid/telephony/ims/ImsService;.CAPABILITIES_LOG_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.2:Ljava/lang/Long; 271 +Landroid/icu/impl/ValidIdentifiers$Datasubtype;.unknown:Landroid/icu/impl/ValidIdentifiers$Datasubtype;.name:Ljava/lang/String; 271 +Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.4:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 271 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.569:Ljava/lang/Long; 272 +Lcom/android/ims/rcs/uce/UceDeviceState;.DEVICE_STATE_DESCRIPTION:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.3:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 273 +Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.0:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 274 +Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.1:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 275 +Landroid/icu/text/MeasureFormat;.hmsTo012:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 276 +Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mMap:Landroid/util/ArrayMap; 277 +Landroid/os/RemoteException; 278 +Landroid/content/pm/PackageManager$OnChecksumsReadyListener; 279 +Landroid/content/pm/Checksum$Type; 280 +Landroid/view/InputEvent;.mNextSeq:Ljava/util/concurrent/atomic/AtomicInteger; 281 +Landroid/view/MotionEvent; 282 +Landroid/animation/PropertyValuesHolder$FloatPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 283 +Landroid/animation/PropertyValuesHolder;.sGetterPropertyMap:Ljava/util/HashMap; 284 +Landroid/graphics/drawable/LayerDrawable; 285 +Landroid/graphics/Bitmap;.sAllBitmaps:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 289 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.11:Ljava/lang/Boolean; 292 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1:Ljava/lang/Boolean; 293 +Landroid/util/TypedValue; 305 +Landroid/os/MessageQueue; 306 +Landroid/view/KeyEvent; 307 +Landroid/app/job/JobServiceEngine$JobHandler; 308 +Landroid/os/AsyncTask$InternalHandler; 309 +Landroid/app/IActivityTaskManager; 310 +Landroid/view/View$$ExternalSyntheticLambda4; 310 +Landroid/view/WindowInsets; 311 +Landroid/app/ActivityTaskManager$2; 311 +Landroid/util/Singleton; 311 +Landroid/view/View$AttachInfo; 311 +Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.3:Ljava/lang/String; 311 +Lcom/android/icu/util/regex/PatternNative; 311 +Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.2:Ljava/lang/String; 311 +Landroid/app/Dialog$ListenersHandler; 311 +Landroid/webkit/WebViewDelegate; 311 +Landroid/hardware/display/IDisplayManager; 311 +Landroid/app/servertransaction/PendingTransactionActions$StopInfo; 312 +Landroid/view/animation/Animation$3; 312 +Landroid/content/pm/IPackageManager; 313 +Landroid/view/ViewRootImpl$8; 314 +Landroid/view/ViewRootImpl$ViewRootHandler; 315 +Landroid/app/LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0; 316 +Landroid/app/LoadedApk$ServiceDispatcher$RunConnection; 317 +Landroid/view/Choreographer$FrameDisplayEventReceiver; 318 +Landroid/view/inputmethod/InputMethodManager$H; 318 +Landroid/graphics/HardwareRendererObserver$$ExternalSyntheticLambda0; 318 +Landroid/view/Choreographer$FrameHandler; 319 +Landroid/app/ActivityThread$H; 320 +Landroid/os/Parcel$ReadWriteHelper; 327 +Landroid/util/SparseArray; 328 +Landroid/app/Instrumentation; 329 +Landroid/app/IActivityManager$Stub$Proxy; 330 +Landroid/content/pm/Attribution; 331 +[Landroid/content/pm/PermissionInfo; 331 +[Landroid/os/PatternMatcher; 331 +Landroid/content/pm/ActivityInfo$WindowLayout; 331 +Landroid/content/pm/ServiceInfo; 331 +[Landroid/content/pm/PathPermission; 331 +Landroid/content/pm/SharedLibraryInfo; 331 +Landroid/content/pm/SigningInfo; 331 +Landroid/content/pm/PathPermission; 331 +Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_APERTURES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 331 +[Landroid/content/pm/ServiceInfo; 331 +[Landroid/content/pm/ProviderInfo; 331 +[Landroid/content/pm/FeatureInfo; 331 +[Landroid/content/pm/Attribution; 331 +Landroid/content/pm/ActivityInfo; 331 +Landroid/os/PatternMatcher; 331 +Landroid/content/pm/ConfigurationInfo; 331 +[Landroid/content/pm/FeatureGroupInfo; 331 +[Landroid/content/pm/InstrumentationInfo; 331 +[Landroid/content/pm/ActivityInfo; 331 +[Landroid/content/pm/ConfigurationInfo; 331 +Landroid/content/pm/InstrumentationInfo; 331 +[Landroid/content/pm/Signature; 331 +Landroid/content/pm/FeatureGroupInfo; 331 +Landroid/view/ViewManager; 332 +Landroid/view/KeyEvent$Callback; 332 +Landroid/view/ViewParent; 332 +Landroid/view/accessibility/AccessibilityEventSource; 332 +Landroid/graphics/drawable/Drawable$Callback; 332 +Landroid/content/pm/SigningDetails; 334 +Landroid/content/pm/ProviderInfo; 334 +Landroid/content/pm/PermissionInfo; 334 +Landroid/content/pm/FeatureInfo; 334 +Landroid/util/Pair; 335 +Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_AVAILABLE_FOCAL_LENGTHS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 336 +primitive F 336 +Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_AVAILABLE_CAPABILITIES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/params/StreamConfiguration; 337 +Landroid/hardware/camera2/params/StreamConfigurationDuration; 337 +Landroid/hardware/camera2/CameraCharacteristics;.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.HEIC_AVAILABLE_HEIC_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +Landroid/hardware/camera2/params/HighSpeedVideoConfiguration; 337 +Landroid/hardware/camera2/CameraCharacteristics;.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 337 +primitive I 337 +Landroid/hardware/camera2/CameraCharacteristics;.SCALER_MULTI_RESOLUTION_STREAM_SUPPORTED:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 338 +Landroid/hardware/camera2/marshal/MarshalRegistry;.sMarshalerMap:Ljava/util/HashMap; 338 +Landroid/hardware/camera2/CameraCharacteristics;.LENS_FACING:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 339 +Landroid/graphics/drawable/NinePatchDrawable; 341 +Landroid/widget/HorizontalScrollView; 342 +Lcom/android/internal/os/PowerProfile;.sPowerItemMap:Ljava/util/HashMap; 343 +Lcom/android/internal/os/PowerProfile;.sModemPowerProfile:Lcom/android/internal/power/ModemPowerProfile;.mPowerConstants:Landroid/util/SparseDoubleArray;.mValues:Landroid/util/SparseLongArray; 343 +Lcom/android/internal/os/PowerProfile;.sPowerArrayMap:Ljava/util/HashMap; 343 +Landroid/content/pm/ComponentInfo; 346 +Landroid/content/pm/Signature; 346 +Landroid/content/pm/PackageItemInfo; 347 +Landroid/content/pm/PackageInfo; 348 +Landroid/os/Parcelable; 349 +Landroid/aconfig/nano/Aconfig$parsed_flag; 355 +Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedString; 355 +Landroid/aconfig/nano/Aconfig$tracepoint; 355 +Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringValueMap; 355 +Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringSet; 355 +Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringList; 355 +Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object; 355 +Lcom/android/internal/util/Parcelling$BuiltIn$ForInternedStringArray; 355 +Landroid/app/ActivityTaskManager; 355 +Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap;.mHashes:[I 355 +Lcom/android/internal/util/Parcelling$Cache;.sCache:Landroid/util/ArrayMap; 355 +Lcom/android/ims/rcs/uce/presence/pidfparser/omapres/Version;.ELEMENT_NAME:Ljava/lang/String; 356 +primitive [[I 356 +Landroid/provider/SyncStateContract$Columns;.DATA:Ljava/lang/String; 356 +Landroid/view/Window$Callback; 356 +Landroid/os/BatteryConsumer;.sPowerComponentNames:[Ljava/lang/String;.13:Ljava/lang/String; 358 +Landroid/graphics/Color;.sColorNameMap:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.3:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 358 +Lcom/android/ims/rcs/uce/presence/pidfparser/pidf/Basic;.OPEN:Ljava/lang/String; 358 +Lcom/android/internal/os/BinderCallsStats$SettingsObserver;.SETTINGS_ENABLED_KEY:Ljava/lang/String; 358 +Landroid/os/IncidentManager;.URI_SCHEME:Ljava/lang/String; 358 +Landroid/text/Html$HtmlParser;.schema:Lorg/ccil/cowan/tagsoup/HTMLSchema;.theEntities:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.3233:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 358 +Landroid/icu/impl/units/UnitsData$Constants;.DEFAULT_USAGE:Ljava/lang/String; 359 +Lcom/android/internal/telephony/IccProvider;.ADDRESS_BOOK_COLUMN_NAMES:[Ljava/lang/String;.0:Ljava/lang/String; 359 +Landroid/app/NotificationChannel;.EDIT_LAUNCHER:Ljava/lang/String; 360 +Lcom/android/internal/os/RailStats;.WIFI_SUBSYSTEM:Ljava/lang/String; 360 +Lcom/android/ims/ImsUt;.KEY_ACTION:Ljava/lang/String; 360 +Landroid/view/textclassifier/TextClassifier;.TYPE_URL:Ljava/lang/String; 360 +Landroid/app/NotificationChannel;.TAG_CHANNEL:Ljava/lang/String; 360 +Landroid/icu/text/MessageFormat;.typeList:[Ljava/lang/String;.5:Ljava/lang/String; 360 +Landroid/icu/impl/ValidIdentifiers$Datatype;.region:Landroid/icu/impl/ValidIdentifiers$Datatype;.name:Ljava/lang/String; 360 +Landroid/provider/Telephony$BaseMmsColumns;.START:Ljava/lang/String; 360 +Lcom/android/ims/rcs/uce/presence/pidfparser/pidf/Timestamp;.ELEMENT_NAME:Ljava/lang/String; 360 +Lcom/android/ims/rcs/uce/presence/pidfparser/pidf/Status;.ELEMENT_NAME:Ljava/lang/String; 360 +Landroid/os/BatteryManager;.EXTRA_SEQUENCE:Ljava/lang/String; 360 +Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.0:Ljava/lang/String; 360 +Landroid/provider/Telephony$ThreadsColumns;.ERROR:Ljava/lang/String; 360 +Lcom/android/ims/ImsManager;.TRUE:Ljava/lang/String; 361 +Ljavax/sip/header/ContentEncodingHeader;.NAME:Ljava/lang/String; 362 +Landroid/widget/CompoundButton; 362 +Landroid/view/View$OnClickListener; 362 +Landroid/content/res/AssetManager; 363 +Landroid/view/ContextThemeWrapper; 364 +Landroid/content/res/Resources; 365 +Landroid/content/res/ResourcesImpl; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.BOTTOM_TOP:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/view/animation/Animation$1; 366 +Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.220:Ljava/lang/String; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.TL_BR:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/view/ViewRootImpl$5; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.RIGHT_LEFT:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/media/MediaPlayer$EventHandler; 366 +Landroid/view/View$$ExternalSyntheticLambda7; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.BR_TL:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.TOP_BOTTOM:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/view/View$$ExternalSyntheticLambda0; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.LEFT_RIGHT:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Lcom/android/internal/policy/PhoneWindow$1; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.BL_TR:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/graphics/drawable/GradientDrawable$Orientation;.TR_BL:Landroid/graphics/drawable/GradientDrawable$Orientation; 366 +Landroid/os/PowerManager$3$$ExternalSyntheticLambda0; 366 +Landroid/hardware/SensorManager; 366 +Landroid/app/SharedPreferencesImpl$EditorImpl$$ExternalSyntheticLambda0; 367 +Landroid/view/View$ScrollabilityCache; 368 +Landroid/graphics/drawable/LevelListDrawable; 368 +Landroid/app/INotificationManager; 368 +Landroid/os/IInterface; 371 +Landroid/app/servertransaction/TopResumedActivityChangeItem; 372 +Landroid/app/servertransaction/ClientTransaction; 373 +Landroid/database/CursorToBulkCursorAdaptor; 375 +Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry; 375 +Landroid/net/MatchAllNetworkSpecifier; 375 +Lcom/android/internal/telephony/TelephonyPermissions;.sReportedDeviceIDPackages:Ljava/util/Map; 375 +Landroid/telephony/AnomalyReporter; 375 +Landroid/telephony/NetworkRegistrationInfo; 375 +Landroid/telephony/VoiceSpecificRegistrationInfo; 375 +Landroid/telephony/TelephonyRegistryManager;.sCarrierPrivilegeCallbacks:Ljava/util/WeakHashMap; 375 +Landroid/content/ContentProvider$Transport; 375 +Landroid/app/PropertyInvalidatedCache;.sInvalidates:Ljava/util/HashMap; 376 +Landroid/app/PropertyInvalidatedCache$NoPreloadHolder; 376 +Landroid/app/PropertyInvalidatedCache;.sDisabledKeys:Ljava/util/HashSet;.map:Ljava/util/HashMap; 377 +Landroid/media/session/MediaSessionManager$OnMediaKeyEventSessionChangedListener; 378 +Landroid/app/ActivityManager$MyUidObserver; 378 +Landroid/media/session/MediaSessionManager$SessionsChangedWrapper$1; 378 +Landroid/app/PendingIntent$CancelListener; 379 +Lcom/android/internal/widget/MessagingLayout; 380 +Lcom/android/internal/widget/ImageFloatingTextView; 380 +Landroid/widget/ProgressBar$RefreshData;.sPool:Landroid/util/Pools$SynchronizedPool; 380 +Landroid/widget/ViewSwitcher;.dexCache:Ljava/lang/Object; 380 +Lcom/android/internal/util/PerfettoTrigger;.sLastInvocationPerTrigger:Landroid/util/SparseLongArray;.mKeys:[I 380 +Landroid/widget/DateTimeView$ReceiverInfo$1; 380 +Landroid/widget/SeekBar; 380 +Lcom/android/internal/colorextraction/ColorExtractor$OnColorsChangedListener; 380 +Landroid/view/RemotableViewMethod; 380 +Landroid/view/ViewGroup$ViewLocationHolder;.sPool:Landroid/util/Pools$SynchronizedPool; 380 +Landroid/view/View;.TRANSLATION_Y:Landroid/util/Property; 380 +Lcom/android/internal/widget/ConversationLayout; 380 +Lcom/android/internal/util/ContrastColorUtil; 380 +Lcom/android/internal/widget/ImageResolver; 380 +Landroid/view/View;.SCALE_X:Landroid/util/Property; 380 +Landroid/view/ViewGroup$ViewLocationHolder;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 380 +Landroid/permission/PermissionManager;.INDICATOR_EXEMPTED_PACKAGES:[Ljava/lang/String; 380 +Landroid/media/MediaRouter2Manager; 380 +Landroid/hardware/display/NightDisplayListener$Callback; 380 +Lcom/android/internal/widget/NotificationOptimizedLinearLayout; 380 +Landroid/hardware/biometrics/BiometricSourceType;.IRIS:Landroid/hardware/biometrics/BiometricSourceType; 380 +Landroid/view/NotificationTopLineView; 380 +Landroid/os/HandlerExecutor; 380 +Lcom/android/internal/widget/RemeasuringLinearLayout; 380 +Lcom/android/internal/util/PerfettoTrigger;.sLastInvocationPerTrigger:Landroid/util/SparseLongArray; 380 +Landroid/animation/ValueAnimator$DurationScaleChangeListener; 380 +Landroid/view/ViewGroup$ChildListForAccessibility;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 380 +Landroid/widget/ProgressBar$RefreshData;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 380 +Landroid/graphics/drawable/DrawableInflater;.CONSTRUCTOR_MAP:Ljava/util/HashMap; 380 +Landroid/transition/TransitionManager;.sPendingTransitions:Ljava/util/ArrayList; 380 +Landroid/view/SurfaceControl; 380 +Lcom/android/internal/widget/CachingIconView; 380 +Landroid/view/animation/AnimationSet; 380 +Landroid/hardware/face/FaceManager$FaceDetectionCallback; 380 +Lcom/android/internal/widget/NotificationExpandButton; 380 +Landroid/view/View;.SCALE_Y:Landroid/util/Property; 380 +Landroid/view/NotificationHeaderView; 380 +Landroid/widget/RemoteViews;.sMethods:Landroid/util/ArrayMap; 380 +Landroid/widget/DateTimeView; 380 +Lcom/android/internal/widget/NotificationActionListLayout; 380 +Landroid/view/accessibility/AccessibilityManager$$ExternalSyntheticLambda0; 380 +Landroid/view/ViewOverlay$OverlayViewGroup; 380 +Landroid/text/TextShaper$GlyphsConsumer; 380 +Landroid/permission/PermissionManager; 380 +Landroid/widget/RemoteViews;.sLookupKey:Landroid/widget/RemoteViews$MethodKey; 380 +Lcom/android/internal/logging/UiEventLogger; 380 +Landroid/hardware/biometrics/BiometricSourceType;.FACE:Landroid/hardware/biometrics/BiometricSourceType; 380 +Lcom/android/internal/view/menu/ActionMenuItemView; 380 +Landroid/hardware/biometrics/BiometricSourceType;.FINGERPRINT:Landroid/hardware/biometrics/BiometricSourceType; 380 +Lcom/android/internal/util/PerfettoTrigger;.sLastInvocationPerTrigger:Landroid/util/SparseLongArray;.mValues:[J 380 +Landroid/app/trust/TrustManager$TrustListener; 380 +Landroid/view/ViewGroup$ChildListForAccessibility;.sPool:Landroid/util/Pools$SynchronizedPool; 380 +Landroid/view/View$OnHoverListener; 381 +Landroid/content/res/ColorStateList; 381 +Landroid/view/inputmethod/EditorInfo; 382 +Landroid/view/Window$DecorCallback; 382 +Landroid/view/MenuItem$OnActionExpandListener; 382 +Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry; 382 +Landroid/widget/inline/InlinePresentationSpec; 383 +Landroid/os/DeadObjectException; 384 +Landroid/content/ContentProviderClient; 385 +Landroid/os/UserHandle;.sExtraUserHandleCache:Landroid/util/SparseArray; 385 +Landroid/util/Log$TerribleFailure; 386 +Landroid/telephony/DataSpecificRegistrationInfo; 387 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle; 388 +Landroid/telephony/NetworkService; 389 +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerEvent; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteController; 390 +Lcom/android/internal/telephony/GsmCdmaCallTracker; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.483:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularServiceState; 390 +Lcom/android/ims/rcs/uce/eab/EabProvider; 390 +Lcom/android/internal/telephony/NetworkTypeController$DefaultState; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierRoamingSatelliteSession; 390 +Lcom/android/internal/telephony/ProxyController; 390 +Lcom/android/internal/telephony/TelephonyComponentFactory; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSosMessageRecommender; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationTermination; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.23:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/DisplayInfoController; 390 +Lcom/android/internal/telephony/DeviceStateMonitor$3; 390 +Landroid/telephony/CellSignalStrengthWcdma; 390 +Landroid/os/Handler$MessengerImpl; 390 +Lcom/android/internal/telephony/TelephonyDevController;.mModems:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/CarrierServiceBindHelper$CarrierServicePackageMonitor; 390 +Landroid/telephony/ims/RegistrationManager$RegistrationCallback$RegistrationBinder; 390 +Lcom/android/internal/telephony/uicc/UiccProfile; 390 +Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaScpTestBroadcastReceiver; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.363:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierIdMismatch; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$UnavailableState; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteProvision; 390 +Lcom/android/internal/telephony/emergency/EmergencyNumberTracker$1; 390 +Lcom/android/internal/telephony/metrics/TelephonyMetrics; 390 +Lcom/android/internal/telephony/RILRequest; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$GbaEvent; 390 +Lcom/android/internal/telephony/TelephonyDevController; 390 +Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker$2; 390 +Lcom/android/internal/telephony/satellite/PointingAppController; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipMessageResponse; 390 +Lcom/android/internal/telephony/InboundSmsHandler$NewMessageNotificationActionReceiver; 390 +Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.831:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/DeviceStateMonitor; 390 +Landroid/telephony/TelephonyRegistryManager$3; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteSession; 390 +Landroid/telephony/data/ApnSetting;.APN_TYPE_INT_MAP:Ljava/util/Map; 390 +Lcom/android/internal/telephony/imsphone/ImsExternalCallTracker; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOffRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Landroid/net/NetworkPolicyManager$SubscriptionCallbackProxy; 390 +Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler; 390 +Lcom/android/internal/telephony/IntentBroadcaster; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.469:[Ljava/lang/String; 390 +Landroid/telephony/BarringInfo$BarringServiceInfo; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.673:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/PackageChangeReceiver; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$14; 390 +Lcom/android/internal/telephony/RadioInterfaceCapabilityController; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallSession; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationStats; 390 +Lcom/android/internal/telephony/SmsStorageMonitor$1; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequestsV2; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.349:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$CarrierRoamingSatelliteControllerStats; 390 +Lcom/android/internal/telephony/security/NullCipherNotifier; 390 +Lcom/android/internal/telephony/uicc/UiccPkcs15$Pkcs15Selector; 390 +Lcom/android/internal/telephony/SMSDispatcher$1; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$DataNetworkValidation; 390 +Lcom/android/internal/telephony/CarrierActionAgent; 390 +Lcom/android/internal/telephony/TelephonyTester$1; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportSession; 390 +Lcom/android/internal/telephony/IntentBroadcaster$1; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mTtyModeReceivedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mMmiCompleteRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSuppServiceFailedRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$PresenceNotifyEvent; 390 +Lcom/android/i18n/timezone/TimeZoneFinder; 390 +Lcom/android/internal/telephony/SimActivationTracker$1; 390 +Lcom/android/internal/telephony/NitzStateMachine; 390 +Lcom/android/internal/telephony/ims/ImsResolver$3; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$DisconnectedState; 390 +Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1457:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/gsm/GsmInboundSmsHandler$GsmCbTestBroadcastReceiver; 390 +Landroid/timezone/TelephonyLookup; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsDedicatedBearerListenerEvent; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$IncomingSms; 390 +Lcom/android/internal/telephony/UiccPhoneBookController; 390 +Lcom/android/internal/telephony/imsphone/ImsPhone; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$AvailableState; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$BindingState; 390 +Lcom/android/internal/telephony/euicc/EuiccCardController; 390 +Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall; 390 +Lcom/android/internal/telephony/uicc/UiccStateChangedLauncher; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisplayInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/TelephonyDevController;.mSims:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/AppSmsManager; 390 +Lcom/android/internal/telephony/SmsUsageMonitor; 390 +Lcom/android/internal/telephony/cat/CatService; 390 +Lcom/android/internal/telephony/GsmCdmaCallTracker$1; 390 +Landroid/telephony/ims/ProvisioningManager$Callback$CallbackBinder; 390 +Landroid/telephony/ims/ImsMmTelManager$CapabilityCallback$CapabilityBinder; 390 +Landroid/telephony/CellSignalStrengthGsm; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$EmergencyNumbersInfo; 390 +Lcom/android/internal/telephony/cdma/CdmaInboundSmsHandler$CdmaCbTestBroadcastReceiver; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1441:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/RIL;.sRilTimeHistograms:Landroid/util/SparseArray; 390 +Landroid/app/timezonedetector/TimeZoneDetector; 390 +Lcom/android/internal/telephony/SmsBroadcastUndelivered; 390 +Landroid/telephony/emergency/EmergencyNumber; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$VoiceCallRatUsage; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$CellularDataServiceSwitch; 390 +Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 390 +Lcom/android/ims/ImsManager;.IMS_STATS_CALLBACKS:Landroid/util/SparseArray;.mKeys:[I 390 +Lcom/android/internal/telephony/CommandException$Error;.INVALID_SIM_STATE:Lcom/android/internal/telephony/CommandException$Error; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mDisconnectRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/PhoneSubInfoController; 390 +Lcom/android/internal/telephony/PhoneFactory; 390 +Lcom/android/internal/telephony/SmsApplication$SmsPackageMonitor; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipTransportFeatureTagStats; 390 +Landroid/telephony/ims/aidl/IImsServiceController$Stub$Proxy; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsAcsProvisioningStats; 390 +Lcom/android/internal/telephony/SmsStorageMonitor; 390 +Lcom/android/internal/telephony/uicc/UiccController; 390 +Lcom/android/internal/telephony/GsmCdmaPhone; 390 +Landroid/telephony/CellSignalStrengthTdscdma; 390 +Lcom/android/internal/telephony/SomeArgs; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mHandlerMap:Ljava/util/HashMap; 390 +Lcom/android/internal/telephony/cdma/CdmaSubscriptionSourceManager; 390 +Lcom/android/internal/telephony/uicc/UiccProfile$2; 390 +Lcom/android/internal/telephony/LocaleTracker; 390 +Lcom/android/internal/telephony/CarrierKeyDownloadManager$3; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$1; 390 +Lcom/android/internal/telephony/CarrierServiceBindHelper$1; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mBackgroundCalls:Ljava/util/ArrayList; 390 +Landroid/telephony/CellSignalStrengthCdma; 390 +Landroid/telephony/TelephonyLocalConnection; 390 +Lcom/android/internal/telephony/RilWakelockInfo; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mForegroundCalls:Ljava/util/ArrayList; 390 +Landroid/os/AsyncResult; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteIncomingDatagram; 390 +Lcom/android/i18n/timezone/TelephonyLookup; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.789:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/euicc/EuiccController; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingSms; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.33:[Ljava/lang/String; 390 +Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.549:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$OutgoingShortCodeSms; 390 +Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo; 390 +Lcom/android/internal/telephony/IWapPushManager; 390 +Lcom/android/internal/telephony/MccTable; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SipDelegateStats; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$UnmeteredNetworks; 390 +Lcom/android/internal/telephony/IccSmsInterfaceManager; 390 +Lcom/android/internal/telephony/PhoneConfigurationManager; 390 +Lcom/android/internal/telephony/ims/ImsServiceController$ImsFeatureStatusCallback$1; 390 +Lcom/android/internal/telephony/uicc/UiccProfile$4; 390 +Lcom/android/internal/telephony/euicc/EuiccCardController$SimSlotStatusChangedBroadcastReceiver; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState; 390 +Lcom/android/internal/telephony/emergency/EmergencyNumberTracker; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteConfigUpdater; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$RcsClientProvisioningStats; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$UceEventStats; 390 +Lcom/android/internal/telephony/ims/ImsResolver$2; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mSignalInfoRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/RadioConfig; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mRingingCalls:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/ims/ImsResolver; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$EuiccPackageMonitor; 390 +Lcom/android/internal/telephony/SmsBroadcastUndelivered$1; 390 +Lcom/android/internal/telephony/SmsApplication; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteOutgoingDatagram; 390 +Lcom/android/internal/telephony/uicc/PinStorage$1; 390 +Lcom/android/internal/telephony/IccPhoneBookInterfaceManager; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$NetworkRequests; 390 +Lcom/android/internal/telephony/ServiceStateTracker; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationServiceDescStats; 390 +Lcom/android/internal/telephony/ServiceStateTracker$1; 390 +Landroid/net/TelephonyNetworkSpecifier; 390 +Landroid/telephony/CellSignalStrengthNr; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mInCallVoicePrivacyOnRegistrants:Lcom/android/internal/telephony/RegistrantList;.registrants:Ljava/util/ArrayList; 390 +Lcom/android/internal/telephony/CallManager;.INSTANCE:Lcom/android/internal/telephony/CallManager;.mPhones:Ljava/util/ArrayList; 390 +Landroid/telephony/ModemActivityInfo; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$ImsRegistrationFeatureTagStats; 390 +Lcom/android/internal/telephony/LocaleTracker$1; 390 +Lcom/android/internal/telephony/SmsDispatchersController; 390 +Landroid/telephony/ModemInfo; 390 +Lcom/android/internal/telephony/CommandException; 390 +Lcom/android/internal/telephony/satellite/SatelliteModemInterface; 390 +Lcom/android/internal/telephony/CarrierPrivilegesTracker$1; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$DataCallSession; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1171:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/SimActivationTracker; 390 +Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyServiceState$NetworkRegistrationInfo; 390 +Landroid/telephony/CellSignalStrengthLte; 390 +Lcom/android/internal/telephony/CarrierActionAgent$1; 390 +Landroid/timezone/TimeZoneFinder; 390 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.1287:[Ljava/lang/String; 390 +Lcom/android/internal/telephony/nano/PersistAtomsProto$SatelliteEntitlement; 390 +Lcom/android/internal/telephony/CarrierResolver$2; 390 +Lcom/android/internal/telephony/CellBroadcastServiceManager; 390 +Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$5; 390 +Lcom/android/internal/telephony/imsphone/ImsPhoneCallTracker; 390 +Lcom/android/internal/telephony/uicc/UiccCarrierPrivilegeRules; 390 +Lcom/android/internal/telephony/NetworkTypeController$1; 390 +Lcom/android/internal/telephony/util/NotificationChannelController$1; 390 +Lcom/android/internal/telephony/StateMachine$SmHandler; 390 +Lcom/android/ims/FeatureConnector$1; 390 +Lcom/android/internal/telephony/ims/ImsResolver$1; 390 +Lcom/android/internal/telephony/MultiSimSettingController; 390 +Landroid/telephony/ims/aidl/IImsConfig$Stub$Proxy; 391 +Landroid/telephony/ims/aidl/IImsRegistration$Stub$Proxy; 391 +Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray; 392 +Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mValues:[Ljava/lang/Object; 392 +Lcom/android/ims/ImsManager;.IMS_MANAGER_INSTANCES:Landroid/util/SparseArray;.mKeys:[I 393 +Landroid/telephony/ims/ImsUtListener; 394 +Landroid/telephony/ims/feature/MmTelFeature$1; 394 +Landroid/telephony/ims/stub/ImsRegistrationImplBase$1; 394 +Landroid/telephony/ims/stub/ImsConfigImplBase$ImsConfigStub; 394 +Landroid/telephony/TelephonyRegistryManager; 396 +Landroid/widget/Button; 397 +Landroid/widget/TextView; 398 +Lcom/android/internal/telephony/MccTable;.FALLBACKS:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.6:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 399 +Landroid/media/AudioManager$OnAudioFocusChangeListener; 400 +primitive [I 401 +Landroid/os/Parcelable$Creator; 402 +Landroid/os/Bundle; 403 +Landroid/graphics/Bitmap$Config;.HARDWARE:Landroid/graphics/Bitmap$Config; 404 +Landroid/graphics/Bitmap$Config;.RGBA_F16:Landroid/graphics/Bitmap$Config; 404 +Landroid/renderscript/Allocation;.mBitmapOptions:Landroid/graphics/BitmapFactory$Options;.inPreferredConfig:Landroid/graphics/Bitmap$Config; 404 +Landroid/graphics/Bitmap$Config;.ALPHA_8:Landroid/graphics/Bitmap$Config; 404 +Landroid/graphics/Bitmap$Config;.RGBA_1010102:Landroid/graphics/Bitmap$Config; 404 +Landroid/graphics/Bitmap$Config;.ARGB_4444:Landroid/graphics/Bitmap$Config; 404 +Landroid/graphics/Bitmap$Config;.RGB_565:Landroid/graphics/Bitmap$Config; 404 +Landroid/icu/util/Calendar;.WEEK_DATA_CACHE:Landroid/icu/util/Calendar$WeekDataCache;.map:Ljava/util/concurrent/ConcurrentHashMap; 407 +Landroid/icu/util/ULocale; 408 +Landroid/graphics/Paint;.sMinikinLocaleListIdCache:Ljava/util/HashMap; 409 +Landroid/icu/text/BreakIterator;.iterCache:[Landroid/icu/impl/CacheValue; 410 +Landroid/widget/EditText; 411 +Landroid/view/autofill/AutofillValue; 412 +Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool;.mPool:[Ljava/lang/Object; 413 +Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;.sPool:Landroid/util/Pools$SimplePool; 413 +Landroid/view/ViewGroup; 414 +Landroid/graphics/Rect; 415 +Landroid/graphics/Insets; 416 +Landroid/content/LocusId; 417 +Landroid/view/contentcapture/ContentCaptureContext; 417 +Landroid/telephony/TelephonyCallback$DisplayInfoListener; 419 +Landroid/app/assist/AssistStructure$HtmlInfoNode; 420 +Landroid/app/ActivityManager$AppTask; 420 +Landroid/database/CursorIndexOutOfBoundsException; 422 +Landroid/media/MediaPlayer; 422 +Lcom/android/internal/policy/DecorView$2; 423 +Landroid/security/keystore2/AndroidKeyStoreRSAPrivateKey; 424 +Landroid/widget/Spinner; 424 +Landroid/app/slice/Slice;.SUBTYPE_SOURCE:Ljava/lang/String; 425 +Ljavax/sip/message/Request;.INFO:Ljava/lang/String; 425 +Lgov/nist/javax/sip/header/AuthenticationHeader;.SIGNATURE:Ljava/lang/String; 426 +Landroid/widget/RadioGroup$OnCheckedChangeListener; 428 +Ljavax/sip/header/AcceptEncodingHeader;.NAME:Ljava/lang/String; 429 +Landroid/app/ReceiverRestrictedContext; 429 +Landroid/content/Context;.ACCOUNT_SERVICE:Ljava/lang/String; 429 +Landroid/service/trust/TrustAgentService;.EXTRA_TOKEN:Ljava/lang/String; 429 +Landroid/view/VelocityTracker;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 429 +Lcom/android/internal/widget/DialogTitle; 429 +Landroid/view/VelocityTracker;.sPool:Landroid/util/Pools$SynchronizedPool; 429 +Landroid/os/StrictMode$OnThreadViolationListener; 429 +Lcom/android/internal/widget/ButtonBarLayout; 429 +Lcom/android/internal/widget/AlertDialogLayout; 429 +Lcom/android/internal/logging/AndroidHandler; 430 +Landroid/webkit/JavascriptInterface; 446 +Landroid/icu/impl/StandardPlural; 447 +Landroid/icu/impl/number/range/StandardPluralRanges; 448 +Landroid/icu/text/PluralRules$Operand; 448 +Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader; 448 +Landroid/icu/impl/PluralRulesLoader;.loader:Landroid/icu/impl/PluralRulesLoader;.pluralRulesCache:Ljava/util/Map; 448 +Landroid/webkit/CookieManager; 449 +Landroid/os/strictmode/Violation; 459 +Lcom/android/internal/telephony/uicc/asn1/Asn1Node;.EMPTY_NODE_LIST:Ljava/util/List; 460 +Landroid/os/strictmode/CredentialProtectedWhileLockedViolation; 462 +Landroid/content/AsyncQueryHandler; 463 +Landroid/widget/CheckedTextView; 464 +Landroid/app/Activity$$ExternalSyntheticLambda0; 464 +Landroid/app/job/JobParameters; 467 +Landroid/os/SystemProperties; 468 +Landroid/hardware/display/DisplayManager$DisplayListener; 470 +Landroid/view/View$OnApplyWindowInsetsListener; 470 +Landroid/view/Choreographer$FrameCallback; 471 +Landroid/os/Handler$Callback; 472 +Landroid/telephony/TelephonyCallback$DataConnectionStateListener; 473 +Landroid/view/WindowManagerImpl; 474 +Landroid/util/DisplayMetrics; 474 +Landroid/content/ServiceConnection; 474 +Landroid/app/ActivityManager$MemoryInfo; 474 +Landroid/view/Display; 474 +Landroid/os/VibrationEffect; 475 +Landroid/view/SurfaceView; 476 +Landroid/content/ContextWrapper; 477 +Landroid/app/Application; 478 +Landroid/view/View$OnSystemUiVisibilityChangeListener; 479 +Landroid/view/View$OnLayoutChangeListener; 480 +Landroid/os/Build$VERSION; 481 +Landroid/view/InputDevice; 482 +Landroid/preference/PreferenceManager; 482 +Landroid/app/SharedPreferencesImpl$EditorImpl; 482 +Landroid/widget/Switch; 486 +Landroid/view/View$OnGenericMotionListener; 487 +Landroid/icu/text/DecimalFormatSymbols;.DEF_DIGIT_STRINGS_ARRAY:[Ljava/lang/String;.1:Ljava/lang/String; 488 +Landroid/widget/Editor$TextRenderNode; 489 +Landroid/database/sqlite/SQLiteCantOpenDatabaseException; 490 +Landroid/accounts/Account; 490 +Landroid/accounts/Account;.sAccessedAccounts:Ljava/util/Set; 491 +Landroid/app/PendingIntent$OnFinished; 492 +Landroid/hardware/location/ContextHubTransaction$OnCompleteListener; 492 +Landroid/os/WorkSource; 492 +Landroid/os/ParcelUuid; 493 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.12:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mLock:Ljava/lang/Object; 493 +Landroid/database/sqlite/SQLiteConstraintException; 493 +Landroid/location/Location; 493 +Landroid/os/strictmode/CustomViolation; 494 +Landroid/graphics/drawable/PictureDrawable; 495 +primitive [C 504 +primitive [S 505 +Landroid/view/AttachedSurfaceControl$OnBufferTransformHintChangedListener; 507 +Landroid/webkit/WebChromeClient$CustomViewCallback; 508 +primitive [B 514 +Landroid/view/Window$OnFrameMetricsAvailableListener; 516 +Landroid/net/Uri$PathPart;.EMPTY:Landroid/net/Uri$PathPart; 521 +Landroid/app/IActivityManager; 522 +Landroid/text/style/ImageSpan; 523 +Landroid/security/keystore2/AndroidKeyStoreECPrivateKey; 524 +Landroid/security/keystore/KeyInfo; 525 +Landroid/view/WindowLeaked; 526 +Landroid/app/ContentProviderHolder; 526 +Landroid/text/style/URLSpan; 526 +Landroid/icu/util/ULocale$AvailableType;.DEFAULT:Landroid/icu/util/ULocale$AvailableType;.name:Ljava/lang/String; 527 +Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.9:Ljava/lang/Integer; 528 +Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_MIME_TYPES:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.11:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 528 +Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.2:Ljava/lang/Integer; 529 +Landroid/text/Html$TagHandler; 530 +Landroid/text/HtmlToSpannedConverter$Font; 531 +Landroid/transition/Explode; 532 +Landroid/content/pm/PackageManager$OnPermissionsChangedListener; 535 +Landroid/os/UserManager; 537 +Landroid/hardware/display/ColorDisplayManager$ColorDisplayManagerInternal; 538 +Landroid/graphics/ColorSpace$Model;.RGB:Landroid/graphics/ColorSpace$Model; 539 +Landroid/app/WallpaperManager; 539 +Landroid/app/Notification; 540 +Landroid/app/RemoteAction; 541 +Landroid/database/sqlite/SQLiteTransactionListener; 542 +Landroid/accounts/OnAccountsUpdateListener; 544 +Landroid/accounts/AccountManager$20; 545 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mLock:Ljava/lang/Object; 548 +Landroid/hardware/SensorEventListener; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.6:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.237:Ljava/lang/String; 553 +Lcom/android/internal/content/om/OverlayConfigParser;.CONFIG_DIRECTORY:Ljava/lang/String; 553 +Lcom/android/internal/app/procstats/DumpUtils;.STATE_TAGS:[Ljava/lang/String;.14:Ljava/lang/String; 553 +Landroid/opengl/GLSurfaceView; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.127:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.141:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.80:Ljava/lang/String; 553 +Landroid/util/AndroidRuntimeException; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.35:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.15:Ljava/lang/String; 553 +Landroid/text/Html$ImageGetter; 553 +Landroid/view/ThreadedRenderer;.OVERDRAW_PROPERTY_SHOW:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._replacementCountries:[Ljava/lang/String;.9:Ljava/lang/String; 553 +Landroid/window/ImeOnBackInvokedDispatcher;.RESULT_KEY_PRIORITY:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.118:Ljava/lang/String; 553 +Landroid/icu/impl/LocaleIDs;._countries:[Ljava/lang/String;.221:Ljava/lang/String; 553 +Landroid/transition/TransitionInflater;.sConstructors:Landroid/util/ArrayMap; 554 +Lcom/android/internal/transition/EpicenterTranslateClipReveal; 554 +Landroid/widget/HorizontalScrollView$SavedState; 555 +Landroid/widget/Spinner$SavedState; 555 +Landroid/widget/AbsSpinner$SavedState; 555 +Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_MIME_TYPES:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.33:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 557 +Landroid/graphics/drawable/ShapeDrawable; 558 +Landroid/text/method/DialerKeyListener; 560 +Lgov/nist/javax/sip/address/NetObject;.PHONE:Ljava/lang/String; 567 +Landroid/app/usage/UsageEvents$Event;.DEVICE_EVENT_PACKAGE_NAME:Ljava/lang/String; 567 +Landroid/view/translation/UiTranslationManager;.EXTRA_PACKAGE_NAME:Ljava/lang/String; 567 +Landroid/icu/text/MessageFormat;.dateModifierList:[Ljava/lang/String;.3:Ljava/lang/String; 567 +Landroid/icu/impl/ValidIdentifiers$Datatype;.language:Landroid/icu/impl/ValidIdentifiers$Datatype;.name:Ljava/lang/String; 567 +Lgov/nist/javax/sip/header/extensions/ReferencesHeader;.SERVICE:Ljava/lang/String; 567 +Landroid/icu/impl/locale/LocaleValidityChecker$SpecialCase;.normal:Landroid/icu/impl/locale/LocaleValidityChecker$SpecialCase;.name:Ljava/lang/String; 567 +Landroid/icu/impl/LocaleIDs;._languages:[Ljava/lang/String;.140:Ljava/lang/String; 568 +Landroid/icu/impl/units/UnitPreferences;.measurementSystem:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.15:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 569 +Lcom/android/i18n/timezone/TimeZoneFinder;.COUNTRY_ELEMENT:Ljava/lang/String; 570 +Landroid/icu/text/MessageFormat;.rootLocale:Ljava/util/Locale;.baseLocale:Lsun/util/locale/BaseLocale;.language:Ljava/lang/String; 571 +Landroid/view/accessibility/AccessibilityManager$TouchExplorationStateChangeListener; 585 +Landroid/graphics/drawable/AnimatedVectorDrawable; 590 +Landroid/view/TextureView; 592 +Landroid/app/Notification$MessagingStyle; 596 +Landroid/os/Message;.sPoolSync:Ljava/lang/Object; 596 +Landroid/os/strictmode/LeakedClosableViolation; 597 +Landroid/view/DisplayCutout; 597 +Landroid/app/AppOpsManager$OnOpNotedListener; 597 +Lcom/android/internal/policy/AttributeCache; 597 +Landroid/app/Notification$CallStyle; 597 +Lcom/android/internal/logging/UiEventLogger$UiEventEnum; 597 +Lcom/android/internal/statusbar/NotificationVisibility; 597 +Landroid/app/AppOpsManager$OnOpNotedInternalListener; 597 +Landroid/window/IRemoteTransition$Stub$Proxy; 597 +Lcom/android/internal/logging/MetricsLogger; 597 +Landroid/hardware/display/DisplayManagerGlobal$DisplayListenerDelegate$$ExternalSyntheticLambda0; 597 +Landroid/app/Notification$DecoratedCustomViewStyle; 597 +Landroid/app/AppOpsManager$OnOpStartedListener; 597 +Lcom/android/internal/R$styleable;.WindowAnimation:[I 597 +Lcom/android/internal/util/LatencyTracker$Action; 597 +Landroid/app/Notification$BigPictureStyle; 598 +Landroid/app/Notification$MediaStyle; 598 +Landroid/app/Notification$InboxStyle; 598 +Landroid/app/Notification$BigTextStyle; 599 +Landroid/app/AppOpsManager$Mode; 600 +Landroid/view/accessibility/AccessibilityManager$AccessibilityServicesStateChangeListener; 601 +Landroid/annotation/IdRes; 602 +Landroid/app/usage/AppStandbyInfo; 603 +Landroid/content/ContentProvider$PipeDataWriter; 604 +Landroid/os/strictmode/UnsafeIntentLaunchViolation; 604 +Landroid/app/servertransaction/ObjectPool;.sPoolMap:Ljava/util/Map; 605 +Landroid/app/servertransaction/ResumeActivityItem; 605 +Landroid/app/servertransaction/ActivityRelaunchItem; 605 +Landroid/os/strictmode/DiskReadViolation; 606 +Landroid/net/metrics/DhcpClientEvent; 607 +Landroid/icu/impl/CharacterPropertiesImpl;.inclusions:[Landroid/icu/text/UnicodeSet; 608 +Lcom/android/internal/policy/PhoneWindow; 616 +Landroid/os/ResultReceiver$MyRunnable; 619 +Landroid/window/ImeOnBackInvokedDispatcher$$ExternalSyntheticLambda0; 620 +Landroid/text/StaticLayout; 620 +Landroid/graphics/SurfaceTexture$1; 620 +Landroid/animation/AnimationHandler$AnimationFrameCallbackProvider; 620 +Landroid/provider/FontsContract;.sTypefaceCache:Landroid/util/LruCache; 622 +Landroid/provider/FontsContract;.sTypefaceCache:Landroid/util/LruCache;.map:Ljava/util/LinkedHashMap; 623 +Landroid/graphics/drawable/InsetDrawable; 624 +Landroid/graphics/PorterDuff$Mode;.SRC_IN:Landroid/graphics/PorterDuff$Mode; 625 +Landroid/widget/CheckBox; 626 +Landroid/widget/RadioButton; 627 +Landroid/content/pm/PackageManager; 628 +Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map;.table:[Ljava/util/HashMap$Node; 631 +Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader;.packages:Ljava/util/Map;.m:Ljava/util/Map; 631 +Landroid/view/textclassifier/TextLanguage;.EMPTY:Landroid/view/textclassifier/TextLanguage;.mBundle:Landroid/os/Bundle;.mClassLoader:Ljava/lang/ClassLoader; 631 +Landroid/content/AttributionSource; 632 +Landroid/widget/GridLayout;.UNDEFINED_ALIGNMENT:Landroid/widget/GridLayout$Alignment; 633 +Landroid/text/Spanned; 636 +Landroid/renderscript/RenderScript; 636 +Lcom/android/internal/policy/PhoneLayoutInflater; 636 +Landroid/content/pm/IPackageManager$Stub$Proxy; 637 +Landroid/widget/ViewSwitcher; 638 +Landroid/app/IActivityTaskManager$Stub$Proxy; 639 +Lcom/android/internal/telephony/ITelephony; 639 +Landroid/content/IntentFilter; 640 +Landroid/telephony/TelephonyCallback$RadioPowerStateListener; 641 +Landroid/telephony/TelephonyCallback$ServiceStateListener; 642 +Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache;.mCache:Ljava/util/LinkedHashMap; 643 +Landroid/app/compat/CompatChanges;.QUERY_CACHE:Landroid/app/compat/ChangeIdStateCache; 643 +Landroid/app/AlarmManager; 644 +Landroid/text/format/DateFormat; 647 +Landroid/icu/text/Collator; 648 +Landroid/widget/TextView$SavedState; 649 +Landroid/widget/ViewFlipper; 661 +Landroid/os/PersistableBundle; 664 +Landroid/content/pm/ShortcutInfo; 665 +Landroid/graphics/drawable/Icon; 666 +Landroid/content/Intent; 667 +Landroid/telephony/CarrierConfigManager;.sDefaults:Landroid/os/PersistableBundle;.mMap:Landroid/util/ArrayMap;.mArray:[Ljava/lang/Object;.197:Landroid/os/PersistableBundle; 668 +Landroid/app/ActivityTaskManager;.sInstance:Landroid/util/Singleton; 672 +Landroid/widget/Space; 677 +Landroid/content/pm/ApplicationInfo; 678 +primitive [J 681 +primitive [D 682 +primitive [Z 683 +primitive [F 684 +Landroid/window/SplashScreen; 685 +Landroid/app/servertransaction/LaunchActivityItem; 689 +Landroid/graphics/Matrix; 691 +Landroid/graphics/RectF; 691 +Landroid/os/Parcel$LazyValue; 691 +Landroid/app/FragmentManagerState; 693 +Landroid/view/AbsSavedState$1; 694 +Lcom/android/internal/policy/PhoneWindow$PanelFeatureState$SavedState; 695 +Landroid/net/Uri; 697 +Landroid/view/View;.sNextGeneratedId:Ljava/util/concurrent/atomic/AtomicInteger; 698 +Landroid/media/MediaRouter$WifiDisplayStatusChangedReceiver; 701 +Landroid/media/MediaRouter$VolumeChangeReceiver; 701 +Landroid/media/MediaRouter; 702 +Landroid/hardware/SensorPrivacyManager; 703 +Landroid/os/storage/StorageManager; 704 +Landroid/credentials/CredentialManager; 705 +Landroid/media/tv/tunerresourcemanager/TunerResourceManager; 705 +Landroid/provider/E2eeContactKeysManager; 705 +Landroid/service/persistentdata/PersistentDataBlockManager; 705 +Landroid/os/HardwarePropertiesManager; 705 +Landroid/app/wearable/WearableSensingManager; 705 +Landroid/net/vcn/VcnManager; 705 +Landroid/app/admin/DevicePolicyManager; 705 +Landroid/app/contentsuggestions/ContentSuggestionsManager; 705 +Landroid/view/textservice/TextServicesManager; 705 +Landroid/view/textclassifier/TextClassificationManager; 705 +Landroid/media/session/MediaSessionManager; 705 +Landroid/view/translation/TranslationManager; 705 +Landroid/view/WindowManager; 705 +Landroid/os/SystemConfigManager; 705 +Landroid/hardware/input/InputManager; 705 +Landroid/permission/PermissionControllerManager; 705 +Landroid/app/people/PeopleManager; 705 +Landroid/app/contextualsearch/ContextualSearchManager; 705 +Landroid/os/RecoverySystem; 705 +Landroid/net/wifi/sharedconnectivity/app/SharedConnectivityManager; 705 +Landroid/security/attestationverification/AttestationVerificationManager; 705 +Landroid/view/autofill/AutofillManager; 705 +Landroid/telephony/SubscriptionManager; 705 +Landroid/view/LayoutInflater; 705 +Landroid/net/NetworkPolicyManager; 705 +Landroid/view/contentcapture/ContentCaptureManager; 705 +Landroid/content/pm/PackageManager$NameNotFoundException; 706 +Landroid/hardware/devicestate/DeviceStateManagerGlobal; 707 +Landroid/telecom/TelecomManager; 708 +Landroid/content/pm/ParceledListSlice; 709 +Landroid/app/NotificationChannelGroup; 709 +Landroid/os/vibrator/StepSegment; 710 +Landroid/security/keystore2/KeyStoreCryptoOperationUtils; 711 +Landroid/security/keystore2/AndroidKeyStoreProvider; 712 +Landroid/widget/ProgressBar; 715 +Landroid/animation/LayoutTransition; 716 +Landroid/widget/ImageButton; 717 +Landroid/graphics/drawable/AdaptiveIconDrawable; 718 +Landroid/widget/ActionMenuView; 719 +Landroid/widget/Toolbar; 719 +Landroid/widget/ActionMenuPresenter$OverflowMenuButton; 719 +Lcom/android/internal/widget/ActionBarContainer; 720 +Lcom/android/internal/widget/ActionBarContainer$ActionBarBackgroundDrawable; 720 +Lcom/android/internal/widget/ActionBarContextView; 720 +Lcom/android/internal/widget/ActionBarOverlayLayout; 720 +Landroid/app/Fragment;.sClassMap:Landroid/util/ArrayMap; 721 +Landroid/graphics/drawable/TransitionDrawable; 722 +Landroid/media/MediaDrm$OnEventListener; 723 +Landroid/widget/MediaController$MediaPlayerControl; 724 +Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PIXEL_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCharacteristics;.INFO_SESSION_CONFIGURATION_QUERY_VERSION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_ACTIVE_ARRAY_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/util/Size; 725 +Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AE_AVAILABLE_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_AF_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCharacteristics;.LENS_INFO_SHADING_MAP_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCaptureSession$StateCallback; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/telephony/ims/ImsService;.CAPABILITIES_LOG_MAP:Ljava/util/Map;.table:[Ljava/lang/Object;.8:Ljava/lang/Long; 725 +Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AF_AVAILABLE_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_AWB_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/view/displayhash/DisplayHashManager; 725 +Landroid/hardware/camera2/CaptureResult;.SENSOR_TIMESTAMP:Landroid/hardware/camera2/CaptureResult$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_EXPOSURE_COMPENSATION:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_AWB_AVAILABLE_MODES:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_ZOOM_RATIO:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/marshal/MarshalRegistry;.sMarshalLock:Ljava/lang/Object; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_AE_TARGET_FPS_RANGE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_MODE:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/usb/UsbManager;.FUNCTION_NAME_TO_CODE:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.13:Ljava/util/HashMap$Node;.next:Ljava/util/HashMap$Node;.next:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 725 +Landroid/hardware/camera2/CaptureRequest;.CONTROL_ENABLE_ZSL:Landroid/hardware/camera2/CaptureRequest$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraDevice$StateCallback; 725 +Landroid/hardware/camera2/CameraCharacteristics;.CONTROL_ZOOM_RATIO_RANGE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCharacteristics;.REQUEST_PARTIAL_RESULT_COUNT:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 725 +Landroid/hardware/camera2/CameraCaptureSession$CaptureCallback; 725 +Landroid/hardware/camera2/CameraCharacteristics;.FLASH_INFO_AVAILABLE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 726 +Landroid/text/format/DateUtils; 727 +Landroid/security/IKeyChainService; 728 +Landroid/app/usage/UsageStats; 729 +Landroid/app/usage/CacheQuotaHint; 730 +Landroid/service/watchdog/ExplicitHealthCheckService$PackageConfig; 730 +Landroid/os/BaseBundle; 731 +Landroid/view/ViewTreeObserver$OnWindowFocusChangeListener; 732 +Landroid/graphics/ColorMatrix;.dexCache:Ljava/lang/Object; 733 +Landroid/view/InsetsAnimationThread; 736 +Lcom/android/internal/jank/InteractionJankMonitor$InstanceHolder; 737 +Lcom/android/internal/jank/InteractionJankMonitor; 737 +Landroid/graphics/Bitmap;.sAllBitmaps:Ljava/util/WeakHashMap;.queue:Ljava/lang/ref/ReferenceQueue; 738 +Lcom/android/internal/os/BackgroundThread; 739 +Landroid/app/PendingIntent; 740 +Landroid/security/net/config/UserCertificateSource$NoPreloadHolder; 741 +Lorg/apache/http/params/HttpParams; 742 +Landroid/app/Service; 745 +Landroid/content/ComponentCallbacks2; 746 +Landroid/content/ComponentCallbacks; 747 +Landroid/widget/AbsListView; 751 +Landroid/widget/ListView; 752 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.1:Ljava/util/WeakHashMap$Entry; 753 +Landroid/widget/TextView;.TEMP_RECTF:Landroid/graphics/RectF; 756 +Landroid/app/Activity; 757 +Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool;.mPool:[Ljava/lang/Object; 758 +Landroid/text/MeasuredParagraph;.sPool:Landroid/util/Pools$SynchronizedPool; 758 +Lcom/android/internal/infra/AndroidFuture; 759 +Landroid/widget/TextView;.TEMP_POSITION:[F 760 +Landroid/text/method/MetaKeyKeyListener;.SYM:Ljava/lang/Object; 761 +Landroid/view/inputmethod/SelectRangeGesture; 761 +Landroid/text/method/MetaKeyKeyListener;.CAP:Ljava/lang/Object; 761 +Landroid/text/method/MetaKeyKeyListener;.ALT:Ljava/lang/Object; 761 +Landroid/view/inputmethod/DeleteGesture; 761 +Landroid/view/inputmethod/SelectGesture; 761 +Landroid/text/method/MetaKeyKeyListener;.SELECTING:Ljava/lang/Object; 761 +Landroid/view/inputmethod/DeleteRangeGesture; 761 +Landroid/view/inputmethod/BaseInputConnection;.COMPOSING:Ljava/lang/Object; 762 +Landroid/text/method/DigitsKeyListener;.sLocaleInstanceCache:Ljava/util/HashMap; 763 +Landroid/app/StackTrace; 764 +Landroid/opengl/EGLConfig; 768 +Landroid/widget/PopupWindow$PopupDecorView; 769 +Landroid/widget/PopupWindow$PopupBackgroundView; 769 +Landroid/view/ViewStub$OnInflateListener; 770 +Landroid/opengl/GLSurfaceView;.sGLThreadManager:Landroid/opengl/GLSurfaceView$GLThreadManager; 772 +Landroid/opengl/GLSurfaceView$Renderer; 773 +Landroid/content/ContentValues; 774 +Landroid/graphics/Point; 774 +Landroid/os/Build; 776 +Landroid/widget/ScrollView; 779 +Landroid/os/FileObserver; 780 +Lcom/android/internal/os/RuntimeInit$KillApplicationHandler; 781 +Landroid/app/ResourcesManager; 782 +Landroid/content/res/ResourcesKey; 782 +Landroid/app/backup/BackupManager; 783 +Lcom/android/internal/util/LatencyTracker; 784 +Lcom/android/internal/util/LatencyTracker$SLatencyTrackerHolder; 784 +Landroid/app/Application$ActivityLifecycleCallbacks; 785 +Landroid/os/Messenger; 786 +Landroid/widget/ProgressBar$SavedState; 789 +Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_PARAMETERS:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.25:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 792 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry;.referent:Ljava/lang/Object;.mSkips:[J 793 +Landroid/os/UserHandle; 794 +Landroid/hardware/usb/UsbManager;.FUNCTION_NAME_TO_CODE:Ljava/util/Map;.table:[Ljava/util/HashMap$Node;.13:Ljava/util/HashMap$Node;.value:Ljava/lang/Object; 796 +Landroid/graphics/drawable/RotateDrawable; 796 +Landroid/database/sqlite/SQLiteException; 797 +Landroid/webkit/WebViewFactory;.sProviderLock:Ljava/lang/Object; 798 +Landroid/content/res/AssetManager$AssetInputStream; 799 +Landroid/util/SparseIntArray; 800 +Landroid/database/ContentObserver; 800 +Landroid/icu/text/NFRule;.ZERO:Ljava/lang/Long; 802 +Landroid/view/View$BaseSavedState; 803 +Landroid/app/ActivityThread$ReceiverData; 804 +Landroid/icu/util/ULocale$AliasReplacer; 805 +Landroid/text/method/TextKeyListener;.ACTIVE:Ljava/lang/Object; 806 +Landroid/text/method/PasswordTransformationMethod; 807 +Landroid/speech/tts/TextToSpeech$Connection$SetupConnectionAsyncTask; 808 +Landroid/speech/tts/TextToSpeech$OnInitListener; 809 +Landroid/icu/util/CodePointMap$RangeOption;.NORMAL:Landroid/icu/util/CodePointMap$RangeOption;.name:Ljava/lang/String; 810 +Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;.EF_SOURCE_PRIORITY:Ljava/util/List;.a:[Ljava/lang/Object;.5:Ljava/lang/Integer; 810 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.5:Ljava/util/WeakHashMap$Entry; 811 +Landroid/window/IWindowContainerToken$Stub$Proxy; 812 +Landroid/telephony/SignalStrength; 813 +Landroid/app/LoadedApk$WarningContextClassLoader; 814 +Landroid/widget/ViewAnimator; 814 +Landroid/util/proto/ProtoStream;.FIELD_TYPE_NAMES:[Ljava/lang/String;.10:Ljava/lang/String; 814 +Lcom/android/internal/telephony/euicc/EuiccController;.EXTRA_OPERATION:Ljava/lang/String; 814 +Lorg/apache/http/conn/ssl/SSLSocketFactory$NoPreloadHolder; 814 +Ljavax/sip/header/PriorityHeader;.NORMAL:Ljava/lang/String; 814 +Landroid/hardware/camera2/CameraCharacteristics;.INFO_DEVICE_STATE_ORIENTATIONS:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 816 +Landroid/hardware/camera2/CameraCharacteristics;.INFO_SUPPORTED_HARDWARE_LEVEL:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 816 +Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_ORIENTATION:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 816 +Landroid/hardware/camera2/CameraCharacteristics;.SENSOR_INFO_PHYSICAL_SIZE:Landroid/hardware/camera2/CameraCharacteristics$Key;.mKey:Landroid/hardware/camera2/impl/CameraMetadataNative$Key; 817 +Landroid/util/Log; 818 +Landroid/text/style/StyleSpan; 819 +Landroid/security/keystore/KeyGenParameterSpec; 819 +Landroid/telephony/TelephonyCallback$DataEnabledListener; 820 +Landroid/icu/impl/ZoneMeta;.REGION_CACHE:Landroid/icu/impl/ICUCache; 822 +Lcom/android/internal/telephony/WspTypeDecoder;.WELL_KNOWN_MIME_TYPES:Ljava/util/HashMap;.table:[Ljava/util/HashMap$Node;.81:Ljava/util/HashMap$Node;.key:Ljava/lang/Object; 823 +Landroid/text/style/QuoteSpan; 824 +Landroid/text/HtmlToSpannedConverter$Strikethrough; 824 +Landroid/text/HtmlToSpannedConverter$Background; 824 +Landroid/text/HtmlToSpannedConverter$Alignment; 824 +Landroid/text/HtmlToSpannedConverter$Foreground; 824 +Landroid/text/method/QwertyKeyListener; 826 +Landroid/graphics/Path$Op; 826 +Landroid/view/OrientationEventListener; 832 +Landroid/animation/PropertyValuesHolder$IntPropertyValuesHolder;.sJNISetterPropertyMap:Ljava/util/HashMap; 834 +Landroid/app/PropertyInvalidatedCache;.sCaches:Ljava/util/WeakHashMap;.table:[Ljava/util/WeakHashMap$Entry;.11:Ljava/util/WeakHashMap$Entry; 835 +Landroid/view/accessibility/AccessibilityManager;.sInstanceSync:Ljava/lang/Object; 836 +Lcom/android/internal/listeners/ListenerTransport; 837 diff --git a/core/api/current.txt b/core/api/current.txt index 354e26b2eb02..ea039a7103a3 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -59034,7 +59034,7 @@ package android.widget { } public static interface CompoundButton.OnCheckedChangeListener { - method public void onCheckedChanged(android.widget.CompoundButton, boolean); + method public void onCheckedChanged(@NonNull android.widget.CompoundButton, boolean); } public abstract class CursorAdapter extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter { @@ -60099,7 +60099,7 @@ package android.widget { } public static interface RadioGroup.OnCheckedChangeListener { - method public void onCheckedChanged(android.widget.RadioGroup, @IdRes int); + method public void onCheckedChanged(@NonNull android.widget.RadioGroup, @IdRes int); } public class RatingBar extends android.widget.AbsSeekBar { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 15e5706db9d1..445a57220757 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11981,6 +11981,7 @@ package android.provider { field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION"; field public static final String ACTION_MANAGE_DOMAIN_URLS = "android.settings.MANAGE_DOMAIN_URLS"; field public static final String ACTION_MANAGE_MORE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS"; + field @FlaggedApi("android.nfc.nfc_action_manage_services_settings") public static final String ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS = "android.settings.MANAGE_OTHER_NFC_SERVICES_SETTINGS"; field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS"; field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 90de7abf845c..4fb35c3d5f5c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -7312,7 +7312,7 @@ public class Activity extends ContextThemeWrapper @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void finish(int finishTask) { if (DEBUG_FINISH_ACTIVITY) { - Log.d("Instrumentation", "finishActivity: finishTask=" + finishTask, new Throwable()); + Log.d(Instrumentation.TAG, "finishActivity: finishTask=" + finishTask, new Throwable()); } if (mParent == null) { int resultCode; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 74e95839b2f5..be70de20c2e2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -16,6 +16,7 @@ package android.app; +import static android.app.Instrumentation.DEBUG_FINISH_ACTIVITY; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; @@ -80,6 +81,7 @@ import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.DisplayMetrics; +import android.util.Log; import android.util.Singleton; import android.util.Size; import android.view.WindowInsetsController.Appearance; @@ -6011,6 +6013,10 @@ public class ActivityManager { * Finishes all activities in this task and removes it from the recent tasks list. */ public void finishAndRemoveTask() { + if (DEBUG_FINISH_ACTIVITY) { + Log.d(Instrumentation.TAG, "AppTask#finishAndRemoveTask: task=" + + getTaskInfo(), new Throwable()); + } try { mAppTaskImpl.finishAndRemoveTask(); } catch (RemoteException e) { diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 89efa9b77a60..d31881265064 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -961,6 +961,17 @@ public abstract class ActivityManagerInternal { @Nullable VoiceInteractionManagerProvider provider); /** + * Get whether or not the previous user's packages will be killed before the user is + * stopped during a user switch. + * + * <p> The primary use case of this method is for {@link com.android.server.SystemService} + * classes to call this API in their + * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent + * restarting any of the previous user's processes that will be killed during the user switch. + */ + public abstract boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId); + + /** * Sets whether the current foreground user (and its profiles) should be stopped after switched * out. */ diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 9d63be7239a0..21396a1a36e5 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -10768,8 +10768,13 @@ public class AppOpsManager { 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; } } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index be270463e576..45852c7d338a 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -98,7 +98,7 @@ public class Instrumentation { */ public static final String REPORT_KEY_STREAMRESULT = "stream"; - private static final String TAG = "Instrumentation"; + static final String TAG = "Instrumentation"; private static final long CONNECT_TIMEOUT_MILLIS = 60_000; diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index fd4d8e90adf9..0cc210b7db41 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -1836,9 +1836,10 @@ public class ResourcesManager { // 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/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index c789af32e2b1..9148e3c3a072 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -37,7 +37,6 @@ flag { } } - flag { name: "onboarding_bugreport_v2_enabled" is_exported: true @@ -403,3 +402,13 @@ flag { 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/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS new file mode 100644 index 000000000000..c6827cc93222 --- /dev/null +++ b/core/java/android/app/appfunctions/OWNERS @@ -0,0 +1,6 @@ +avayvod@google.com +oadesina@google.com +toki@google.com +tonymak@google.com +mingweiliao@google.com +anothermark@google.com diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 111e6a8e93ef..cb57c7bd565d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -7485,7 +7485,7 @@ public class Intent implements Parcelable, Cloneable { /** * This flag is only used for split-screen multi-window mode. The new activity will be displayed - * adjacent to the one launching it. This can only be used in conjunction with + * adjacent to the one launching it if possible. This can only be used in conjunction with * {@link #FLAG_ACTIVITY_NEW_TASK}. Also, setting {@link #FLAG_ACTIVITY_MULTIPLE_TASK} is * required if you want a new instance of an existing activity to be created. */ diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index e370e85278d5..273519b75f93 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -151,6 +151,16 @@ flag { } 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" @@ -278,6 +288,13 @@ flag { } flag { + name: "stop_previous_user_apps" + namespace: "multiuser" + description: "Stop the previous user apps early in a user switch" + bug: "323200731" +} + +flag { name: "disable_private_space_items_on_home" namespace: "profile_experiences" description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically" @@ -386,4 +403,7 @@ flag { description: "Refactorings related to unicorn mode to work on HSUM mode (Read only flag)" bug: "339201286" is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 678bd6bc6336..de1cac47ff46 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -415,7 +415,7 @@ public class BiometricManager { @RequiresPermission(TEST_BIOMETRIC) public BiometricTestSession createTestSession(int sensorId) { try { - return new BiometricTestSession(mContext, sensorId, + return new BiometricTestSession(mContext, getSensorProperties(), sensorId, (context, sensorId1, callback) -> mService .createTestSession(sensorId1, callback, context.getOpPackageName())); } catch (RemoteException e) { diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java index 027d1015a4b5..8bd352888de1 100644 --- a/core/java/android/hardware/biometrics/BiometricTestSession.java +++ b/core/java/android/hardware/biometrics/BiometricTestSession.java @@ -27,12 +27,15 @@ import android.os.RemoteException; 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 @@ public class BiometricTestSession implements AutoCloseable { @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 class TestSessionCallbackIml extends ITestSessionCallback.Stub { + private final int mSensorId; + private TestSessionCallbackIml(int sensorId) { + mSensorId = sensorId; + } - private final ITestSessionCallback mCallback = new ITestSessionCallback.Stub() { @Override public void onCleanupStarted(int userId) { Log.d(getTag(), "onCleanupStarted, sensor: " + mSensorId + ", userId: " + userId); @@ -76,19 +87,30 @@ public class BiometricTestSession implements AutoCloseable { 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 @@ public class BiometricTestSession implements AutoCloseable { @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 @@ public class BiometricTestSession implements AutoCloseable { /** * 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 @@ public class BiometricTestSession implements AutoCloseable { /** * 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 @@ public class BiometricTestSession implements AutoCloseable { 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 @@ public class BiometricTestSession implements AutoCloseable { // 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 df9f504a2c05..bd99606808b7 100644 --- a/core/java/android/hardware/biometrics/ITestSession.aidl +++ b/core/java/android/hardware/biometrics/ITestSession.aidl @@ -59,4 +59,8 @@ interface ITestSession { // 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/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 0c55ed5323a0..9bd4860e7ccc 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -17,8 +17,6 @@ package android.hardware.camera2.params; -import static com.android.internal.util.Preconditions.*; - import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -32,8 +30,6 @@ import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraDevice.CameraDeviceSetup; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.impl.CameraMetadataNative; -import android.hardware.camera2.params.InputConfiguration; -import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.HashCodeHelpers; import android.media.ImageReader; import android.os.Parcel; @@ -46,6 +42,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -95,8 +92,8 @@ public final class SessionConfiguration implements Parcelable { public @interface SessionMode {}; // Camera capture session related parameters. - private List<OutputConfiguration> mOutputConfigurations; - private CameraCaptureSession.StateCallback mStateCallback; + private final @NonNull List<OutputConfiguration> mOutputConfigurations; + private CameraCaptureSession.StateCallback mStateCallback = null; private int mSessionType; private Executor mExecutor = null; private InputConfiguration mInputConfig = null; @@ -268,7 +265,8 @@ public final class SessionConfiguration implements Parcelable { */ @Override public int hashCode() { - return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(), mInputConfig.hashCode(), + return HashCodeHelpers.hashCode(mOutputConfigurations.hashCode(), + Objects.hashCode(mInputConfig), mSessionType); } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 903e91646332..7f1cac08b430 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -172,7 +172,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @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/os/ExternalVibrationScale.aidl b/core/java/android/os/ExternalVibrationScale.aidl index cf6f8ed52f7d..644beced2091 100644 --- a/core/java/android/os/ExternalVibrationScale.aidl +++ b/core/java/android/os/ExternalVibrationScale.aidl @@ -33,12 +33,24 @@ parcelable ExternalVibrationScale { 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/Trace.java b/core/java/android/os/Trace.java index edb3a641f107..4a37e0a70443 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -520,8 +520,20 @@ public final class Trace { * @param counterValue The counter value. */ public static void setCounter(@NonNull String counterName, long counterValue) { - if (isTagEnabled(TRACE_TAG_APP)) { - nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue); + setCounter(TRACE_TAG_APP, counterName, counterValue); + } + + /** + * Writes trace message to indicate the value of a given counter under a given trace tag. + * + * @param traceTag The trace tag. + * @param counterName The counter name to appear in the trace. + * @param counterValue The counter value. + * @hide + */ + public static void setCounter(long traceTag, @NonNull String counterName, long counterValue) { + if (isTagEnabled(traceTag)) { + nativeTraceCounter(traceTag, counterName, counterValue); } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 06c516aee8f3..28f2c2530ae9 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4824,6 +4824,7 @@ public class UserManager { * <p>Note that this does not alter the user's pre-existing user restrictions. * * @param userId the id of the user to become admin + * @throws SecurityException if changing ADMIN status of the user is not allowed * @hide */ @RequiresPermission(allOf = { @@ -4844,6 +4845,7 @@ public class UserManager { * <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 f3ef9e15b8f0..e68b74683292 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -663,6 +663,15 @@ public abstract class VibrationEffect implements Parcelable { * @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/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index a4164e9f204c..e6e5a27bd731 100644 --- a/core/java/android/os/vibrator/VibrationConfig.java +++ b/core/java/android/os/vibrator/VibrationConfig.java @@ -49,8 +49,22 @@ import java.util.Arrays; */ 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 @@ public class VibrationConfig { /** @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 @@ public class VibrationConfig { 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 @@ public class VibrationConfig { 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 @@ public class VibrationConfig { } /** + * 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 class VibrationConfig { public String toString() { return "VibrationConfig{" + "mIgnoreVibrationsOnWirelessCharger=" + mIgnoreVibrationsOnWirelessCharger + + ", mDefaultVibrationAmplitude=" + mDefaultVibrationAmplitude + ", mHapticChannelMaxVibrationAmplitude=" + mHapticChannelMaxVibrationAmplitude + ", mRampStepDurationMs=" + mRampStepDurationMs + ", mRampDownDurationMs=" + mRampDownDurationMs @@ -258,6 +295,7 @@ public class VibrationConfig { 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 1a19bb28fc71..53a1a67dfc58 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -103,3 +103,13 @@ flag { 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 a26c6f434e15..a95ce7914d8b 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java @@ -104,7 +104,7 @@ public final class VibrationXmlSerializer { 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 @@ public final class VibrationXmlSerializer { xmlSerializer.endDocument(); } - private static XmlSerializedVibration<VibrationEffect> toSerializedVibration( + private static XmlSerializedVibration<? extends VibrationEffect> toSerializedVibration( VibrationEffect effect, @Flags int flags) throws SerializationFailedException { - XmlSerializedVibration<VibrationEffect> serializedVibration; + XmlSerializedVibration<? extends VibrationEffect> serializedVibration; int serializerFlags = 0; if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) { serializerFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 791b306f5f47..7ca40ea23d57 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2287,6 +2287,26 @@ public final class Settings { "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> @@ -20171,6 +20191,36 @@ public final class Settings { */ 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 @@ -20218,14 +20268,6 @@ public final class Settings { 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 d019bad68cd5..6eaef78ff608 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4985,6 +4985,16 @@ public final class Telephony { */ 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 @@ public final class Telephony { 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 17d2790eac96..013ec5f35761 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -28,7 +28,9 @@ import android.os.RemoteException; 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 @@ public abstract class DreamOverlayService extends Service { // 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 @@ public abstract class DreamOverlayService extends Service { }); } - 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 @@ public abstract class DreamOverlayService extends Service { } } + @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/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 224379b4fd98..fc6c2e88779f 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -25,7 +25,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; 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; @@ -76,8 +75,9 @@ import android.util.PluralsMessageFormatter; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import androidx.annotation.VisibleForTesting; + import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -1074,7 +1074,7 @@ public class ZenModeConfig implements Parcelable { rt.manualRule.type = AutomaticZenRule.TYPE_OTHER; rt.manualRule.condition = new Condition( rt.manualRule.conditionId != null ? rt.manualRule.conditionId - : Uri.EMPTY, "", STATE_TRUE); + : Uri.EMPTY, "", Condition.STATE_TRUE); } } return rt; @@ -2540,10 +2540,34 @@ public class ZenModeConfig implements Parcelable { } public static class ZenRule implements Parcelable { + + /** No manual override. Rule owner can decide its state. */ + public static final int OVERRIDE_NONE = 0; + /** + * User has manually activated a mode. This will temporarily overrule the rule owner's + * decision to deactivate it (see {@link #reconsiderConditionOverride}). + */ + public static final int OVERRIDE_ACTIVATE = 1; + /** + * User has manually deactivated an active mode, or setting ZEN_MODE_OFF (for the few apps + * still allowed to do that) snoozed the mode. This will temporarily overrule the rule + * owner's decision to activate it (see {@link #reconsiderConditionOverride}). + */ + public static final int OVERRIDE_DEACTIVATE = 2; + + @IntDef(prefix = { "OVERRIDE" }, value = { + OVERRIDE_NONE, + OVERRIDE_ACTIVATE, + OVERRIDE_DEACTIVATE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConditionOverride {} + @UnsupportedAppUsage public boolean enabled; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean snoozing; // user manually disabled this instance + @Deprecated + public boolean snoozing; // user manually disabled this instance. Obsolete with MODES_UI @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public String name; // required for automatic @UnsupportedAppUsage @@ -2579,6 +2603,15 @@ public class ZenModeConfig implements Parcelable { // ZenPolicy, so we store them here, only for the manual rule. @FlaggedApi(Flags.FLAG_MODES_UI) int legacySuppressedEffects; + /** + * Signals a user's action to (temporarily or permanently) activate or deactivate this + * rule, overruling the condition set by the owner. This value is not stored to disk, as + * it shouldn't survive reboots or be involved in B&R. It might be reset by certain + * owner-provided state transitions as well. + */ + @FlaggedApi(Flags.FLAG_MODES_UI) + @ConditionOverride + int conditionOverride = OVERRIDE_NONE; public ZenRule() { } @@ -2620,6 +2653,7 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { disabledOrigin = source.readInt(); legacySuppressedEffects = source.readInt(); + conditionOverride = source.readInt(); } } } @@ -2698,6 +2732,7 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { dest.writeInt(disabledOrigin); dest.writeInt(legacySuppressedEffects); + dest.writeInt(conditionOverride); } } } @@ -2708,9 +2743,16 @@ public class ZenModeConfig implements Parcelable { .append("id=").append(id) .append(",state=").append(condition == null ? "STATE_FALSE" : Condition.stateToString(condition.state)) - .append(",enabled=").append(String.valueOf(enabled).toUpperCase()) - .append(",snoozing=").append(snoozing) - .append(",name=").append(name) + .append(",enabled=").append(String.valueOf(enabled).toUpperCase()); + + if (Flags.modesUi()) { + sb.append(",conditionOverride=") + .append(conditionOverrideToString(conditionOverride)); + } else { + sb.append(",snoozing=").append(snoozing); + } + + sb.append(",name=").append(name) .append(",zenMode=").append(Global.zenModeToString(zenMode)) .append(",conditionId=").append(conditionId) .append(",pkg=").append(pkg) @@ -2753,6 +2795,15 @@ public class ZenModeConfig implements Parcelable { return sb.append(']').toString(); } + private static String conditionOverrideToString(@ConditionOverride int value) { + return switch(value) { + case OVERRIDE_ACTIVATE -> "OVERRIDE_ACTIVATE"; + case OVERRIDE_DEACTIVATE -> "OVERRIDE_DEACTIVATE"; + case OVERRIDE_NONE -> "OVERRIDE_NONE"; + default -> "UNKNOWN"; + }; + } + /** @hide */ // TODO: add configuration activity public void dumpDebug(ProtoOutputStream proto, long fieldId) { @@ -2763,7 +2814,11 @@ public class ZenModeConfig implements Parcelable { proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime); proto.write(ZenRuleProto.ENABLED, enabled); proto.write(ZenRuleProto.ENABLER, enabler); - proto.write(ZenRuleProto.IS_SNOOZING, snoozing); + if (Flags.modesApi() && Flags.modesUi()) { + proto.write(ZenRuleProto.IS_SNOOZING, conditionOverride == OVERRIDE_DEACTIVATE); + } else { + proto.write(ZenRuleProto.IS_SNOOZING, snoozing); + } proto.write(ZenRuleProto.ZEN_MODE, zenMode); if (conditionId != null) { proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString()); @@ -2816,7 +2871,8 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { finalEquals = finalEquals && other.disabledOrigin == disabledOrigin - && other.legacySuppressedEffects == legacySuppressedEffects; + && other.legacySuppressedEffects == legacySuppressedEffects + && other.conditionOverride == conditionOverride; } } @@ -2832,7 +2888,8 @@ public class ZenModeConfig implements Parcelable { zenDeviceEffects, modified, allowManualInvocation, iconResName, triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields, - deletionInstant, disabledOrigin, legacySuppressedEffects); + deletionInstant, disabledOrigin, legacySuppressedEffects, + conditionOverride); } else { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, @@ -2858,8 +2915,74 @@ public class ZenModeConfig implements Parcelable { } } + // TODO: b/333527800 - Rename to isActive() public boolean isAutomaticActive() { - return enabled && !snoozing && getPkg() != null && isTrueOrUnknown(); + if (Flags.modesApi() && Flags.modesUi()) { + if (!enabled || getPkg() == null) { + return false; + } else if (conditionOverride == OVERRIDE_ACTIVATE) { + return true; + } else if (conditionOverride == OVERRIDE_DEACTIVATE) { + return false; + } else { + return isTrueOrUnknown(); + } + } else { + return enabled && !snoozing && getPkg() != null && isTrueOrUnknown(); + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + @ConditionOverride + public int getConditionOverride() { + if (Flags.modesApi() && Flags.modesUi()) { + return conditionOverride; + } else { + return snoozing ? OVERRIDE_DEACTIVATE : OVERRIDE_NONE; + } + } + + public void setConditionOverride(@ConditionOverride int value) { + if (Flags.modesApi() && Flags.modesUi()) { + conditionOverride = value; + } else { + if (value == OVERRIDE_ACTIVATE) { + Slog.wtf(TAG, "Shouldn't set OVERRIDE_ACTIVATE if MODES_UI is off"); + } else if (value == OVERRIDE_DEACTIVATE) { + snoozing = true; + } else if (value == OVERRIDE_NONE) { + snoozing = false; + } + } + } + + public void resetConditionOverride() { + setConditionOverride(OVERRIDE_NONE); + } + + /** + * Possibly remove the override, depending on the rule owner's intended state. + * + * <p>This allows rule owners to "take over" manually-provided state with their smartness, + * but only once both agree. + * + * <p>For example, a manually activated rule wins over rule owner's opinion that it should + * be off, until the owner says it should be on, at which point it will turn off (without + * manual intervention) when the rule owner says it should be off. And symmetrically for + * manual deactivation (which used to be called "snoozing"). + */ + public void reconsiderConditionOverride() { + if (Flags.modesApi() && Flags.modesUi()) { + if (conditionOverride == OVERRIDE_ACTIVATE && isTrueOrUnknown()) { + resetConditionOverride(); + } else if (conditionOverride == OVERRIDE_DEACTIVATE && !isTrueOrUnknown()) { + resetConditionOverride(); + } + } else { + if (snoozing && !isTrueOrUnknown()) { + snoozing = false; + } + } } public String getPkg() { diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java index a37e2277f0d1..05c2a9c26709 100644 --- a/core/java/android/service/notification/ZenModeDiff.java +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -454,6 +454,8 @@ public class ZenModeDiff { */ public static class RuleDiff extends BaseDiff { public static final String FIELD_ENABLED = "enabled"; + public static final String FIELD_CONDITION_OVERRIDE = "conditionOverride"; + @Deprecated public static final String FIELD_SNOOZING = "snoozing"; public static final String FIELD_NAME = "name"; public static final String FIELD_ZEN_MODE = "zenMode"; @@ -507,8 +509,15 @@ public class ZenModeDiff { if (from.enabled != to.enabled) { addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled)); } - if (from.snoozing != to.snoozing) { - addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing)); + if (Flags.modesApi() && Flags.modesUi()) { + if (from.conditionOverride != to.conditionOverride) { + addField(FIELD_CONDITION_OVERRIDE, + new FieldDiff<>(from.conditionOverride, to.conditionOverride)); + } + } else { + if (from.snoozing != to.snoozing) { + addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing)); + } } if (!Objects.equals(from.name, to.name)) { addField(FIELD_NAME, new FieldDiff<>(from.name, to.name)); diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 1b85191015a1..bb3f6c9928ac 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -266,4 +266,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +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 + } +} diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index a7641c07bb90..9e4b27d3fa55 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3574,7 +3574,7 @@ public final class SurfaceControl implements Parcelable { 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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b8a885e64acb..1c0700f69ab6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2820,6 +2820,12 @@ public final class ViewRootImpl implements ViewParent, 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; } /** diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index 0b67cad0112b..9931aea41913 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -328,7 +328,7 @@ public class Chronometer extends TextView { if (running) { updateText(SystemClock.elapsedRealtime()); dispatchChronometerTick(); - postDelayed(mTickRunnable, 1000); + postTickOnNextSecond(); } else { removeCallbacks(mTickRunnable); } @@ -342,11 +342,17 @@ public class Chronometer extends TextView { 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 63f8ee7528f2..ed6ec32fca25 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -267,7 +267,7 @@ public abstract class CompoundButton extends Button implements Checkable { * @param buttonView The compound button view whose state has changed. * @param isChecked The new checked state of buttonView. */ - void onCheckedChanged(CompoundButton buttonView, boolean isChecked); + void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked); } /** diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index d445fdc01564..70fe6d5b5c9c 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -366,7 +366,7 @@ public class RadioGroup extends LinearLayout { * @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/TextView.java b/core/java/android/widget/TextView.java index ac899f4c2b5e..61ecc6264ffa 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -28,10 +28,10 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; +import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; -import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import android.R; import android.annotation.CallSuper; @@ -937,6 +937,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextPaint mTempTextPaint; private Object mTempCursor; + private Matrix mTempMatrix; @UnsupportedAppUsage private BoringLayout.Metrics mBoring; @@ -12106,6 +12107,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private PointF convertFromScreenToContentCoordinates(PointF point) { + if (Flags.handwritingGestureWithTransformation()) { + if (mTempMatrix == null) { + mTempMatrix = new Matrix(); + } + Matrix matrix = mTempMatrix; + matrix.reset(); + transformMatrixToLocal(matrix); + matrix.postTranslate( + -viewportToContentHorizontalOffset(), + -viewportToContentVerticalOffset() + ); + + float[] copy = new float[] { point.x, point.y }; + matrix.mapPoints(copy); + return new PointF(copy[0], copy[1]); + } int[] screenToViewport = getLocationOnScreen(); PointF copy = new PointF(point); copy.offset( @@ -12115,6 +12132,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private RectF convertFromScreenToContentCoordinates(RectF rect) { + if (Flags.handwritingGestureWithTransformation()) { + if (mTempMatrix == null) { + mTempMatrix = new Matrix(); + } + Matrix matrix = mTempMatrix; + matrix.reset(); + transformMatrixToLocal(matrix); + matrix.postTranslate( + -viewportToContentHorizontalOffset(), + -viewportToContentVerticalOffset() + ); + + RectF copy = new RectF(rect); + matrix.mapRect(copy); + return copy; + } int[] screenToViewport = getLocationOnScreen(); RectF copy = new RectF(rect); copy.offset( @@ -14279,6 +14312,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Don't use, it returns wrong result when the view is scaled. This method can be removed once + * Flags.handwritingGestureWithTransformation is enabled. + * Assume * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates. * This method obtains the view's visible rectangle whereas the method * {@link #getContentVisibleRect} returns the text layout's visible rectangle. @@ -14299,6 +14335,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Don't use, it returns wrong result when view is scaled. This method can be removed once + * Flags.handwritingGestureWithTransformation is enabled. * Helper method to set {@code rect} to the text content's non-clipped area in the view's * coordinates. * @@ -14314,6 +14352,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); } + private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds, + @Nullable RectF handwritingBounds) { + if (mTempRect == null) { + mTempRect = new Rect(); + } + Rect rect = mTempRect; + if (!getGlobalVisibleRect(rect)) { + return false; + } + if (mTempMatrix == null) { + mTempMatrix = new Matrix(); + } + + Matrix matrix = mTempMatrix; + matrix.reset(); + transformMatrixToLocal(matrix); + editorBounds.set(rect); + // When the view has transformations like scaleX/scaleY computing the global visible + // rectangle will already apply the transformations. The getLocalVisibleRect only offsets + // the global rectangle to local. And the result is wrong the View is scaled. + // + // This approach use the local transformation matrix to map the global rectangle to + // local instead. + // + // Note: it doesn't work well with rotation. Because Rect must be + // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's + // bounding box is stored at Rect instead. It makes the returned Rect larger than + // the correct size. + matrix.mapRect(editorBounds); + + if (handwritingBounds != null) { + // Similar to editorBounds, handwritingBounds must be computed in global coordinates + // and then converted back to local coordinates. Otherwise, if the view is scaled, + // the handwritingBoundsOffsets are also scaled, which is not the expected behavior. + handwritingBounds.top = rect.top - getHandwritingBoundsOffsetTop(); + handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft(); + handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom(); + handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight(); + matrix.mapRect(handwritingBounds); + } + return true; + } + + private boolean getContentVisibleRect(RectF rect) { + if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) { + return false; + } + // Clip the view's visible rect with the text layout's visible rect. + return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), + getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); + } + /** * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} * @@ -14333,9 +14423,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // character bounds in this case yet. return; } - final Rect rect = new Rect(); - getContentVisibleRect(rect); - final RectF visibleRect = new RectF(rect); + final RectF visibleRect = new RectF(); + + if (Flags.handwritingGestureWithTransformation()) { + getContentVisibleRect(visibleRect); + } else { + final Rect rect = new Rect(); + getContentVisibleRect(rect); + visibleRect.set(rect); + } final float[] characterBounds = getCharacterBounds(startIndex, endIndex, viewportToContentHorizontalOffset, viewportToContentVerticalOffset); @@ -14438,24 +14534,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener builder.setMatrix(viewToScreenMatrix); if (includeEditorBounds) { - if (mTempRect == null) { - mTempRect = new Rect(); - } - final Rect bounds = mTempRect; - final RectF editorBounds; - final RectF handwritingBounds; - if (getViewVisibleRect(bounds)) { - editorBounds = new RectF(bounds); - handwritingBounds = new RectF(editorBounds); - handwritingBounds.top -= getHandwritingBoundsOffsetTop(); - handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); - handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); - handwritingBounds.right += getHandwritingBoundsOffsetRight(); + final RectF editorBounds = new RectF(); + final RectF handwritingBounds = new RectF(); + if (Flags.handwritingGestureWithTransformation()) { + getEditorAndHandwritingBounds(editorBounds, handwritingBounds); } else { - // The editor is not visible at all, return empty rectangles. We still need to + if (mTempRect == null) { + mTempRect = new Rect(); + } + final Rect bounds = mTempRect; + + // If the editor is not visible at all, return empty rectangles. We still need to // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. - editorBounds = new RectF(); - handwritingBounds = new RectF(); + if (getViewVisibleRect(bounds)) { + editorBounds.set(bounds); + handwritingBounds.set(editorBounds); + handwritingBounds.top -= getHandwritingBoundsOffsetTop(); + handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); + handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); + handwritingBounds.right += getHandwritingBoundsOffsetRight(); + } } EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) @@ -14533,29 +14631,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } 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)); - - 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); + 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); + } + } + } 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/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index d5746e58ffe6..9aeccf4c3d9b 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -65,6 +65,14 @@ flag { } 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 { } flag { + name: "reduce_keyguard_transitions" + namespace: "windowing_frontend" + description: "Avoid setting keyguard transitions ready unless there are no other changes" + bug: "354647472" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "transit_ready_tracking" namespace: "windowing_frontend" description: "Enable accurate transition readiness tracking" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index b8c2a5f8eb6b..a6ae948604a5 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -19,13 +19,6 @@ flag { flag { namespace: "windowing_sdk" - name: "fullscreen_dim_flag" - description: "Whether to allow showing fullscreen dim on ActivityEmbedding split" - bug: "293797706" -} - -flag { - namespace: "windowing_sdk" name: "activity_embedding_interactive_divider_flag" description: "Whether the interactive divider feature is enabled" bug: "293654166" diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index f72a5ca2bffb..f210741e070b 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.FloatRange; 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 @@ import com.android.internal.graphics.cam.Cam; * * 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 @@ public final class ColorUtils { 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 1df85c389322..49fa37bd0ed3 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 @@ package com.android.internal.graphics.cam; import android.annotation.NonNull; import android.annotation.Nullable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import com.android.internal.graphics.ColorUtils; @@ -25,6 +26,7 @@ import com.android.internal.graphics.ColorUtils; * 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 f54172996168..76fabc6529d8 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 @@ package com.android.internal.graphics.cam; import android.annotation.NonNull; import android.graphics.Color; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import com.android.internal.graphics.ColorUtils; @@ -45,6 +46,7 @@ import com.android.internal.graphics.ColorUtils; * 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 0ac7cbc2f60e..c419fabc9d89 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 @@ import com.android.internal.annotations.VisibleForTesting; * 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 d7a869185cd7..6e558e7809a5 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 @@ package com.android.internal.graphics.cam; * 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/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index fbec1f104fc8..e0c90d83768c 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -332,6 +332,8 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto } private void onTracingFlush() { + Log.d(LOG_TAG, "Executing onTracingFlush"); + final ExecutorService loggingService; try { mBackgroundServiceLock.lock(); @@ -352,15 +354,19 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto 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 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto 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 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto 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 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto 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 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto } 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 000000000000..3dab2e39b852 --- /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 index 0b13a1a66e6f..2333a062d897 100644 --- a/core/java/com/android/internal/protolog/ProtoLogService.java +++ b/core/java/com/android/internal/protolog/ProtoLogService.java @@ -33,6 +33,8 @@ 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; @@ -43,6 +45,7 @@ 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; @@ -224,6 +227,14 @@ public final class ProtoLogService extends IProtoLogService.Stub { 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. diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java index 15ecedd0f59e..cd7dcfdac906 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.os.VibrationEffect; 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 index 23df3048e69c..6c562c99565e 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.io.IOException; 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 @@ final class SerializedVibrationEffect implements XmlSerializedVibration<Vibratio @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 @@ final class SerializedVibrationEffect implements XmlSerializedVibration<Vibratio @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 db5c7ff830b5..862f7cb1d476 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.annotation.Nullable; 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 8924311f9c33..a6f48a445564 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.annotation.NonNull; 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 000000000000..aa1b0a236723 --- /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 2b8b61d50a6c..a9fbcafa128d 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 @@ package com.android.internal.vibrator.persistence; 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 @@ import java.util.List; * } * </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 @@ public class VibrationEffectXmlParser { /** * 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 @@ public class VibrationEffectXmlParser { * <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 @@ public class VibrationEffectXmlParser { 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 @@ public class VibrationEffectXmlParser { 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 f561c1485f1d..d74a23d47f4a 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 @@ import java.util.List; * <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 @@ public final class VibrationEffectXmlSerializer { /** * 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 @@ public final class VibrationEffectXmlSerializer { 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 @@ public final class VibrationEffectXmlSerializer { "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 SerializedVendorEffect serializeVendorEffect( + VibrationEffect.VendorEffect effect) { + return new SerializedVendorEffect(effect.getVendorData()); } - private static SerializedVibrationEffect serializePrimitiveEffect( + private static SerializedComposedEffect serializePrimitiveEffect( VibrationEffect.Composed effect) throws XmlSerializerException { List<VibrationEffectSegment> segments = effect.getSegments(); XmlValidator.checkSerializerCondition(effect.getRepeatIndex() == -1, @@ -95,10 +106,10 @@ public final class VibrationEffectXmlSerializer { 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 @@ public final class VibrationEffectXmlSerializer { 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 8b92153b27db..2a55d999bc0f 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 final class XmlConstants { 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 6712f1c46a50..000000000000 --- 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 7507864eea38..e2b30e74b0d5 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 @@ import org.xmlpull.v1.XmlPullParserException; /** * 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 a5ace8438142..0ac6fefc8cb2 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 @@ public final class XmlReader { } /** + * 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 3233fa224694..c20b7d2026ec 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 @@ import java.io.IOException; * 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 102e6c1db395..000000000000 --- 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 c57ff5d50cd2..2e7ad090cf0f 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 @@ package com.android.internal.vibrator.persistence; /** * 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 84d4f3f49e8a..1b5a3561c3ef 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 @@ package com.android.internal.vibrator.persistence; 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 @@ public final class XmlValidator { * 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/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 03b5143ac1f7..db6fb23d3173 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1023,10 +1023,22 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf, "--compiler-filter=", "-Ximage-compiler-option"); - // If there is a dirty-image-objects file, push it. - if (hasFile("/system/etc/dirty-image-objects")) { - addOption("-Ximage-compiler-option"); - addOption("--dirty-image-objects=/system/etc/dirty-image-objects"); + // If there are dirty-image-objects files, push them. + const char* dirty_image_objects_options[] = { + // Currently, there are two dirty-image-objects files: one for + // ART module, one for framework. + "--dirty-image-objects=/system/etc/dirty-image-objects.txt", + "--dirty-image-objects=/apex/com.android.art/etc/dirty-image-objects.txt", + // Allow old filename (without .txt) for backward compatibility. + "--dirty-image-objects=/system/etc/dirty-image-objects", + }; + for (const char* option : dirty_image_objects_options) { + // Get the file path by finding the first '/' and check if + // this file exists. + if (hasFile(strchr(option, '/'))) { + addOption("-Ximage-compiler-option"); + addOption(option); + } } parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j", diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 12804d43b04e..e7f0560612cc 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -163,7 +163,7 @@ message VibratorManagerServiceDumpProto { optional bool vibrator_under_external_control = 5; optional bool low_power_mode = 6; optional bool vibrate_on = 24; - optional bool keyboard_vibration_on = 25; + reserved 25; // prev keyboard_vibration_on optional int32 default_vibration_amplitude = 26; optional int32 alarm_intensity = 18; optional int32 alarm_default_intensity = 19; diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml index a30be6a13db6..5854e816d2b0 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/layout/time_picker_text_input_material.xml b/core/res/res/layout/time_picker_text_input_material.xml index 4988842cb99c..86070b1773dc 100644 --- a/core/res/res/layout/time_picker_text_input_material.xml +++ b/core/res/res/layout/time_picker_text_input_material.xml @@ -34,19 +34,29 @@ android:layoutDirection="ltr"> <EditText android:id="@+id/input_hour" - android:layout_width="50dp" + android:layout_width="50sp" android:layout_height="wrap_content" + android:layout_alignEnd="@id/hour_label_holder" android:inputType="number" android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" android:imeOptions="actionNext"/> - <TextView - android:id="@+id/label_hour" + <!-- Ensure the label_hour takes up at least 50sp of space --> + <FrameLayout + android:id="@+id/hour_label_holder" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/input_hour" - android:layout_alignStart="@id/input_hour" - android:labelFor="@+id/input_hour" - android:text="@string/time_picker_hour_label"/> + android:layout_below="@id/input_hour"> + <TextView + android:id="@+id/label_hour" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:labelFor="@+id/input_hour" + android:text="@string/time_picker_hour_label"/> + <Space + android:layout_width="50sp" + android:layout_height="0dp"/> + </FrameLayout> <TextView android:id="@+id/input_separator" @@ -58,21 +68,30 @@ <EditText android:id="@+id/input_minute" - android:layout_width="50dp" + android:layout_width="50sp" android:layout_height="wrap_content" android:layout_alignBaseline="@id/input_hour" android:layout_toEndOf="@id/input_separator" android:inputType="number" android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" /> - <TextView - android:id="@+id/label_minute" + <!-- Ensure the label_minute takes up at least 50sp of space --> + <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/input_minute" android:layout_alignStart="@id/input_minute" - android:labelFor="@+id/input_minute" - android:text="@string/time_picker_minute_label"/> - + > + <TextView + android:id="@+id/label_minute" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:labelFor="@+id/input_minute" + android:text="@string/time_picker_minute_label"/> + <Space + android:layout_width="50sp" + android:layout_height="0dp"/> + </FrameLayout> <TextView android:visibility="invisible" android:id="@+id/label_error" diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 77b5587e77be..f397ef2b151c 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/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 5793bbe306f1..2bbaf9cb0cda 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -249,6 +249,7 @@ android_ravenwood_test { ], 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 c05ea3d65562..fc3c2f31459f 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 000000000000..f209c4193afa --- /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/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java index 05fc0e04515c..cf398db22d16 100644 --- a/core/tests/coretests/src/android/colormodel/CamTest.java +++ b/core/tests/coretests/src/android/colormodel/CamTest.java @@ -18,9 +18,12 @@ package com.android.internal.graphics.cam; 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 @@ public final class CamTest { 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/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index b990f2486f9e..e240a0853f46 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1434,8 +1434,47 @@ public class ViewRootImplTest { } @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 000000000000..aaed4307eda3 --- /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 000000000000..3c738372377a --- /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/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java index bf35ed0a1601..2362a4c925f9 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 org.junit.Test; 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 @@ import java.util.stream.Stream; public class CujTest { private static final String ENUM_NAME_PREFIX = "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__"; - private static final Set<String> DEPRECATED_VALUES = new HashSet<>() { - { - add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"); - } - }; - private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() { - { - put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")); - put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")); - put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")); - put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR")); - put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")); - put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE")); - put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE")); - put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE")); - put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")); - put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")); - put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")); - } - }; + private static final Set<String> DEPRECATED_VALUES = Set.of( + ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION" + ); + private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = Map.ofEntries( + Map.entry(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")), + Map.entry(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")), + Map.entry(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")), + Map.entry( + Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, + getEnumName("SHADE_HEADS_UP_DISAPPEAR")), + Map.entry(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")), + Map.entry( + Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, + getEnumName("NOTIFICATION_SHADE_SWIPE")), + Map.entry( + Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, + getEnumName("SHADE_QS_EXPAND_COLLAPSE")), + Map.entry( + Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, + getEnumName("SHADE_QS_SCROLL_SWIPE")), + Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")), + Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")), + Map.entry(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")) + ); @Rule public final Expect mExpect = Expect.create(); diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp index dd86094127da..efb843735aef 100644 --- a/core/tests/resourceflaggingtests/Android.bp +++ b/core/tests/resourceflaggingtests/Android.bp @@ -22,54 +22,6 @@ package { default_team: "trendy_team_android_resources", } -genrule { - name: "resource-flagging-test-app-resources-compile", - tools: ["aapt2"], - srcs: [ - "flagged_resources_res/values/bools.xml", - ], - out: ["values_bools.arsc.flat"], - cmd: "$(location aapt2) compile $(in) -o $(genDir) " + - "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", -} - -genrule { - name: "resource-flagging-test-app-resources-compile2", - tools: ["aapt2"], - srcs: [ - "flagged_resources_res/values/bools2.xml", - ], - out: ["values_bools2.arsc.flat"], - cmd: "$(location aapt2) compile $(in) -o $(genDir) " + - "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", -} - -genrule { - name: "resource-flagging-test-app-apk", - tools: ["aapt2"], - // The first input file in the list must be the manifest - srcs: [ - "TestAppAndroidManifest.xml", - ":resource-flagging-test-app-resources-compile", - ":resource-flagging-test-app-resources-compile2", - ], - out: ["resapp.apk"], - cmd: "$(location aapt2) link -o $(out) --manifest $(in)", -} - -java_genrule { - name: "resource-flagging-apk-as-resource", - srcs: [ - ":resource-flagging-test-app-apk", - ], - out: ["apks_as_resources.res.zip"], - tools: ["soong_zip"], - - cmd: "mkdir -p $(genDir)/res/raw && " + - "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " + - "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", -} - android_test { name: "ResourceFlaggingTests", srcs: [ @@ -82,6 +34,6 @@ android_test { "testng", "compatibility-device-util-axt", ], - resource_zips: [":resource-flagging-apk-as-resource"], + resource_zips: [":resource-flagging-test-app-apk-as-resource"], test_suites: ["device-tests"], } diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java index ad8542e0f6b3..c1e357864fff 100644 --- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java +++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java @@ -69,11 +69,23 @@ public class ResourceFlaggingTest { } private boolean getBoolean(String name) { - int resId = mResources.getIdentifier(name, "bool", "com.android.intenal.flaggedresources"); + int resId = mResources.getIdentifier( + name, + "bool", + "com.android.intenal.flaggedresources"); assertThat(resId).isNotEqualTo(0); return mResources.getBoolean(resId); } + private String getString(String name) { + int resId = mResources.getIdentifier( + name, + "string", + "com.android.intenal.flaggedresources"); + assertThat(resId).isNotEqualTo(0); + return mResources.getString(resId); + } + private String extractApkAndGetPath(int id) throws Exception { final Resources resources = mContext.getResources(); try (InputStream is = resources.openRawResource(id)) { diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java index e9a08aef4856..97f1d5e77ddb 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.hardware.vibrator.IVibrator; 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 @@ import org.junit.runners.JUnit4; 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 @@ public class PrimitiveSegmentTest { } @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 @@ public class PrimitiveSegmentTest { } @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 @@ public class PrimitiveSegmentTest { } @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 01013ab69f85..bea82931dda7 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.hardware.vibrator.IVibrator; 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 @@ import org.junit.runners.JUnit4; 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 @@ public class RampSegmentTest { } @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 @@ public class RampSegmentTest { } @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 40776abc0291..411074a75e2e 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.hardware.vibrator.IVibrator; 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 @@ import org.junit.runners.JUnit4; @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 @@ public class StepSegmentTest { } @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 @@ public class StepSegmentTest { } @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 @@ public class StepSegmentTest { } @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 bf9a820aca5c..1cc38ded1c2c 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 android.os.vibrator.persistence.VibrationXmlParser.isSupportedMime 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 @@ import java.util.Set; @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 @@ public class VibrationEffectXmlSerializationTest { } } + @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 @@ public class VibrationEffectXmlSerializationTest { () -> 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 f0e13c4e8ca3..280b40516b7e 100644 --- a/core/xsd/vibrator/vibration/schema/current.txt +++ b/core/xsd/vibrator/vibration/schema/current.txt @@ -41,9 +41,11 @@ package com.android.internal.vibrator.persistence { 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 fcd250b4fc06..21a6facad242 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 b9de6914b9dc..d35d777d4450 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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index f1e7ef5ce123..99716e7cc69e 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // 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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container); final boolean isFillParent = relativeBounds.isEmpty(); final boolean dimOnTask = !isFillParent - && Flags.fullscreenDimFlag() && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK; final IBinder fragmentToken = container.getTaskFragmentToken(); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 5135e9ee14bc..1c3d9c30c91c 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -166,6 +166,16 @@ java_library { }, } +java_library { + name: "WindowManager-Shell-lite-proto", + + srcs: ["src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto"], + + proto: { + type: "lite", + }, +} + filegroup { name: "wm_shell-shared-aidls", @@ -215,6 +225,7 @@ android_library { "androidx.core_core-animation", "androidx.core_core-ktx", "androidx.arch.core_core-runtime", + "androidx.datastore_datastore", "androidx.compose.material3_material3", "androidx-constraintlayout_constraintlayout", "androidx.dynamicanimation_dynamicanimation", @@ -225,6 +236,7 @@ android_library { "//frameworks/libs/systemui:iconloader_base", "com_android_wm_shell_flags_lib", "WindowManager-Shell-proto", + "WindowManager-Shell-lite-proto", "WindowManager-Shell-shared", "perfetto_trace_java_protos", "dagger2", diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 0a8166fb1a8d..df5f1e46a03c 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -139,6 +139,10 @@ <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string> <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]--> <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> + <!-- Click action label for bubbles to expand menu. [CHAR LIMIT=30]--> + <string name="bubble_accessibility_action_expand_menu">expand menu</string> + <!-- Click action label for bubbles to collapse menu. [CHAR LIMIT=30]--> + <string name="bubble_accessibility_action_collapse_menu">collapse menu</string> <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]--> <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string> <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]--> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index f9a1d940c734..dc511be59764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -357,7 +357,9 @@ public class BadgedImageView extends ConstraintLayout { void showBadge() { Bitmap appBadgeBitmap = mBubble.getAppBadge(); - if (appBadgeBitmap == null) { + final boolean isAppLaunchIntent = (mBubble instanceof Bubble) + && ((Bubble) mBubble).isAppLaunchIntent(); + if (appBadgeBitmap == null || isAppLaunchIntent) { mAppIcon.setVisibility(GONE); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 7dbbb04e4406..5cd2cb7d51d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; @@ -246,7 +247,23 @@ public class Bubble implements BubbleViewProvider { mAppIntent = intent; mDesiredHeight = Integer.MAX_VALUE; mPackageName = intent.getPackage(); + } + private Bubble(ShortcutInfo info, Executor mainExecutor) { + mGroupKey = null; + mLocusId = null; + mFlags = 0; + mUser = info.getUserHandle(); + mIcon = info.getIcon(); + mIsAppBubble = false; + mKey = getBubbleKeyForShortcut(info); + mShowBubbleUpdateDot = false; + mMainExecutor = mainExecutor; + mTaskId = INVALID_TASK_ID; + mAppIntent = null; + mDesiredHeight = Integer.MAX_VALUE; + mPackageName = info.getPackage(); + mShortcutInfo = info; } /** Creates an app bubble. */ @@ -263,6 +280,13 @@ public class Bubble implements BubbleViewProvider { mainExecutor); } + /** Creates a shortcut bubble. */ + public static Bubble createShortcutBubble( + ShortcutInfo info, + Executor mainExecutor) { + return new Bubble(info, mainExecutor); + } + /** * Returns the key for an app bubble from an app with package name, {@code packageName} on an * Android user, {@code user}. @@ -273,6 +297,14 @@ public class Bubble implements BubbleViewProvider { return KEY_APP_BUBBLE + ":" + user.getIdentifier() + ":" + packageName; } + /** + * Returns the key for a shortcut bubble using {@code packageName}, {@code user}, and the + * {@code shortcutInfo} id. + */ + public static String getBubbleKeyForShortcut(ShortcutInfo info) { + return info.getPackage() + ":" + info.getUserId() + ":" + info.getId(); + } + @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, @@ -888,6 +920,17 @@ public class Bubble implements BubbleViewProvider { return mIntent; } + /** + * Whether this bubble represents the full app, i.e. the intent used is the launch + * intent for an app. In this case we don't show a badge on the icon. + */ + public boolean isAppLaunchIntent() { + if (Flags.enableBubbleAnything() && mAppIntent != null) { + return mAppIntent.hasCategory("android.intent.category.LAUNCHER"); + } + return false; + } + @Nullable PendingIntent getDeleteIntent() { return mDeleteIntent; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 949a7236434a..29520efd70b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1335,6 +1335,40 @@ public class BubbleController implements ConfigurationChangeListener, } /** + * Expands and selects a bubble created or found via the provided shortcut info. + * + * @param info the shortcut info for the bubble. + */ + public void expandStackAndSelectBubble(ShortcutInfo info) { + if (!Flags.enableBubbleAnything()) return; + Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } + } + + /** + * Expands and selects a bubble created or found for this app. + * + * @param intent the intent for the bubble. + */ + public void expandStackAndSelectBubble(Intent intent) { + if (!Flags.enableBubbleAnything()) return; + Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } + } + + /** * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble * exists for this entry, and it is able to bubble, a new bubble will be created. * @@ -2323,6 +2357,7 @@ public class BubbleController implements ConfigurationChangeListener, * @param entry the entry to bubble. */ static boolean canLaunchInTaskView(Context context, BubbleEntry entry) { + if (Flags.enableBubbleAnything()) return true; PendingIntent intent = entry.getBubbleMetadata() != null ? entry.getBubbleMetadata().getIntent() : null; @@ -2439,6 +2474,16 @@ public class BubbleController implements ConfigurationChangeListener, } @Override + public void showShortcutBubble(ShortcutInfo info) { + mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(info)); + } + + @Override + public void showAppBubble(Intent intent) { + mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent)); + } + + @Override public void showBubble(String key, int topOnScreen) { mMainExecutor.execute( () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen)); @@ -2634,6 +2679,13 @@ public class BubbleController implements ConfigurationChangeListener, } @Override + public void expandStackAndSelectBubble(ShortcutInfo info) { + mMainExecutor.execute(() -> { + BubbleController.this.expandStackAndSelectBubble(info); + }); + } + + @Override public void expandStackAndSelectBubble(Bubble bubble) { mMainExecutor.execute(() -> { BubbleController.this.expandStackAndSelectBubble(bubble); 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 b6da761b0f9c..3c6c6fa0d8d5 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 @@ -23,8 +23,10 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.LocusId; import android.content.pm.ShortcutInfo; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -421,23 +423,19 @@ public class BubbleData { Bubble bubbleToReturn = getBubbleInStackWithKey(key); if (bubbleToReturn == null) { - bubbleToReturn = getOverflowBubbleWithKey(key); - if (bubbleToReturn != null) { - // Promoting from overflow - mOverflowBubbles.remove(bubbleToReturn); - if (mOverflowBubbles.isEmpty()) { - mStateChange.showOverflowChanged = true; + // Check if it's in the overflow + bubbleToReturn = findAndRemoveBubbleFromOverflow(key); + if (bubbleToReturn == null) { + if (entry != null) { + // Not in the overflow, have an entry, so it's a new bubble + bubbleToReturn = new Bubble(entry, + mBubbleMetadataFlagListener, + mCancelledListener, + mMainExecutor); + } else { + // If there's no entry it must be a persisted bubble + bubbleToReturn = persistedBubble; } - } else if (mPendingBubbles.containsKey(key)) { - // Update while it was pending - bubbleToReturn = mPendingBubbles.get(key); - } else if (entry != null) { - // New bubble - bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener, - mMainExecutor); - } else { - // Persisted bubble being promoted - bubbleToReturn = persistedBubble; } } @@ -448,6 +446,46 @@ public class BubbleData { return bubbleToReturn; } + Bubble getOrCreateBubble(ShortcutInfo info) { + String bubbleKey = Bubble.getBubbleKeyForShortcut(info); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor); + } + return bubbleToReturn; + } + + Bubble getOrCreateBubble(Intent intent) { + UserHandle user = UserHandle.of(mCurrentUserId); + String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), + user); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor); + } + return bubbleToReturn; + } + + @Nullable + private Bubble findAndRemoveBubbleFromOverflow(String key) { + Bubble bubbleToReturn = getBubbleInStackWithKey(key); + if (bubbleToReturn != null) { + return bubbleToReturn; + } + bubbleToReturn = getOverflowBubbleWithKey(key); + if (bubbleToReturn != null) { + mOverflowBubbles.remove(bubbleToReturn); + // Promoting from overflow + mOverflowBubbles.remove(bubbleToReturn); + if (mOverflowBubbles.isEmpty()) { + mStateChange.showOverflowChanged = true; + } + } else if (mPendingBubbles.containsKey(key)) { + bubbleToReturn = mPendingBubbles.get(key); + } + return bubbleToReturn; + } + /** * When this method is called it is expected that all info in the bubble has completed loading. * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index fdb45239fa63..a0c0a25d97a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -232,6 +232,9 @@ public class BubbleExpandedView extends LinearLayout { fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() + || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); + if (mBubble.isAppBubble()) { Context context = mContext.createContextAsUser( @@ -246,7 +249,8 @@ public class BubbleExpandedView extends LinearLayout { /* options= */ null); mTaskView.startActivity(pi, /* fillInIntent= */ null, options, launchBounds); - } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) { + } else if (!mIsOverflow && isShortcutBubble) { + ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey()); options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 5e2141aa639e..5f8f0fd0c54c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -36,6 +36,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.taskview.TaskView; /** @@ -110,6 +111,8 @@ public class BubbleTaskViewHelper { fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() + || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); if (mBubble.isAppBubble()) { Context context = mContext.createContextAsUser( @@ -124,7 +127,7 @@ public class BubbleTaskViewHelper { /* options= */ null); mTaskView.startActivity(pi, /* fillInIntent= */ null, options, launchBounds); - } else if (mBubble.hasMetadataShortcutId()) { + } else if (isShortcutBubble) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 589dfd24624e..9a27fb65ac2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -23,6 +23,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.graphics.drawable.Icon; import android.hardware.HardwareBuffer; @@ -118,6 +119,14 @@ public interface Bubbles { /** * Request the stack expand if needed, then select the specified Bubble as current. + * If no bubble exists for this entry, one is created. + * + * @param info the shortcut info to use to create the bubble. + */ + void expandStackAndSelectBubble(ShortcutInfo info); + + /** + * Request the stack expand if needed, then select the specified Bubble as current. * * @param bubble the bubble to be selected */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 0907ddd1de83..5c789749412c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles; import android.content.Intent; import android.graphics.Rect; +import android.content.pm.ShortcutInfo; import com.android.wm.shell.bubbles.IBubblesListener; import com.android.wm.shell.common.bubbles.BubbleBarLocation; @@ -48,4 +49,8 @@ interface IBubbles { oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10; oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11; + + oneway void showShortcutBubble(in ShortcutInfo info) = 12; + + oneway void showAppBubble(in Intent intent) = 13; }
\ No newline at end of file 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 24c568c23bf2..b7834dbcf07f 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 @@ -32,6 +32,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import com.android.wm.shell.R; @@ -188,12 +189,30 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView // Handle view needs to draw on top of task view. bringChildToFront(mHandleView); + + mHandleView.setAccessibilityDelegate(new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, getResources().getString( + R.string.bubble_accessibility_action_expand_menu))); + } + }); } mMenuViewController = new BubbleBarMenuViewController(mContext, this); mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { @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/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index 8389c819f8ea..0300869cbbe1 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 @@ -23,7 +23,9 @@ import android.graphics.Color; import android.graphics.drawable.Icon; import android.util.AttributeSet; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -71,6 +73,16 @@ public class BubbleBarMenuView extends LinearLayout { mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title); mBubbleDismissIconView = findViewById(R.id.bubble_bar_manage_menu_dismiss_icon); updateThemeColors(); + + mBubbleSectionView.setAccessibilityDelegate(new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.ACTION_CLICK, getResources().getString( + R.string.bubble_accessibility_action_collapse_menu))); + } + }); } private void updateThemeColors() { 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 000000000000..1875675296a8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/OWNERS @@ -0,0 +1,4 @@ +# WM shell sub-module compat ui owners +mariiasand@google.com +gracielawputri@google.com +mcarli@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt index a520d5e60fe5..022906cf568c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt @@ -16,6 +16,9 @@ package com.android.wm.shell.compatui.api +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.protolog.ShellProtoLogGroup + /** * Defines the predicates to invoke for understanding if a component can be created or destroyed. */ @@ -39,6 +42,7 @@ class CompatUILifecyclePredicates( * Describes each compat ui component to the framework. */ class CompatUISpec( + val log: (String) -> Unit = { str -> ProtoLog.v(ShellProtoLogGroup.WM_SHELL_COMPAT_UI, str) }, // Unique name for the component. It's used for debug and for generating the // unique component identifier in the system. val name: String, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index b8b62a76c568..e787a3d94000 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 @@ -72,6 +72,7 @@ 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.desktopmode.education.data.AppHandleEducationDatastoreRepository; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.GlobalDragListener; import com.android.wm.shell.freeform.FreeformComponents; @@ -679,6 +680,13 @@ public abstract class WMShellModule { return new DesktopModeEventLogger(); } + @WMSingleton + @Provides + static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository( + Context context) { + return new AppHandleEducationDatastoreRepository(context); + } + // // Drag and drop // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 400882a8da9a..05c9d02a0de7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -73,25 +73,9 @@ class DesktopModeEventLogger { sessionId, taskUpdate.instanceId ) - FrameworkStatsLog.write( - DESKTOP_MODE_TASK_UPDATE_ATOM_ID, - /* task_event */ + logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED, - /* instance_id */ - taskUpdate.instanceId, - /* uid */ - taskUpdate.uid, - /* task_height */ - taskUpdate.taskHeight, - /* task_width */ - taskUpdate.taskWidth, - /* task_x */ - taskUpdate.taskX, - /* task_y */ - taskUpdate.taskY, - /* session_id */ - sessionId - ) + sessionId, taskUpdate) } /** @@ -105,25 +89,9 @@ class DesktopModeEventLogger { sessionId, taskUpdate.instanceId ) - FrameworkStatsLog.write( - DESKTOP_MODE_TASK_UPDATE_ATOM_ID, - /* task_event */ + logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED, - /* instance_id */ - taskUpdate.instanceId, - /* uid */ - taskUpdate.uid, - /* task_height */ - taskUpdate.taskHeight, - /* task_width */ - taskUpdate.taskWidth, - /* task_x */ - taskUpdate.taskX, - /* task_y */ - taskUpdate.taskY, - /* session_id */ - sessionId - ) + sessionId, taskUpdate) } /** @@ -137,10 +105,16 @@ class DesktopModeEventLogger { sessionId, taskUpdate.instanceId ) + logTaskUpdate( + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, + sessionId, taskUpdate) + } + + private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) { FrameworkStatsLog.write( DESKTOP_MODE_TASK_UPDATE_ATOM_ID, /* task_event */ - FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, + taskEvent, /* instance_id */ taskUpdate.instanceId, /* uid */ 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 97abda81d12d..65f12cf4a196 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 @@ fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean { @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 78d41b211abe..f54b44b29683 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 @@ -1070,6 +1070,11 @@ class DesktopTasksController( // In some launches home task is moved behind new task being launched. Make sure // that's not the case for launches in desktop. moveHomeTask(wct, toTop = false) + // Move existing minimized tasks behind Home + taskRepository.getFreeformTasksInZOrder(task.displayId) + .filter { taskId -> taskRepository.isMinimizedTask(taskId) } + .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) } + .forEach { taskInfo -> wct.reorder(taskInfo.token, /* onTop= */ false) } // Desktop Mode is already showing and we're launching a new Task - we might need to // minimize another Task. val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 38675129ce57..597637d3fbfc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -70,11 +70,11 @@ class DesktopTasksLimiter ( // TODO(b/333018485): replace this observer when implementing the minimize-animation private inner class MinimizeTransitionObserver : TransitionObserver { - private val mPendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() - private val mActiveTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() + private val activeTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() fun addPendingTransitionToken(transition: IBinder, taskDetails: TaskDetails) { - mPendingTransitionTokensAndTasks[transition] = taskDetails + pendingTransitionTokensAndTasks[transition] = taskDetails } override fun onTransitionReady( @@ -83,9 +83,7 @@ class DesktopTasksLimiter ( startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction ) { - val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return - taskToMinimize.transitionInfo = info - mActiveTransitionTokensAndTasks[transition] = taskToMinimize + val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return @@ -97,6 +95,8 @@ class DesktopTasksLimiter ( return } + taskToMinimize.transitionInfo = info + activeTransitionTokensAndTasks[transition] = taskToMinimize this@DesktopTasksLimiter.markTaskMinimized( taskToMinimize.displayId, taskToMinimize.taskId) } @@ -121,7 +121,7 @@ class DesktopTasksLimiter ( } override fun onTransitionStarting(transition: IBinder) { - val mActiveTaskDetails = mActiveTransitionTokensAndTasks[transition] + val mActiveTaskDetails = activeTransitionTokensAndTasks[transition] if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) { // Begin minimize window CUJ instrumentation. interactionJankMonitor.begin( @@ -132,11 +132,11 @@ class DesktopTasksLimiter ( } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - if (mActiveTransitionTokensAndTasks.remove(merged) != null) { + if (activeTransitionTokensAndTasks.remove(merged) != null) { interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) } - mPendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> - mPendingTransitionTokensAndTasks[playing] = taskToTransfer + pendingTransitionTokensAndTasks.remove(merged)?.let { taskToTransfer -> + pendingTransitionTokensAndTasks[playing] = taskToTransfer } } @@ -144,14 +144,14 @@ class DesktopTasksLimiter ( ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: transition %s finished", transition) - if (mActiveTransitionTokensAndTasks.remove(transition) != null) { + if (activeTransitionTokensAndTasks.remove(transition) != null) { if (aborted) { interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) } else { interactionJankMonitor.end(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW) } } - mPendingTransitionTokensAndTasks.remove(transition) + pendingTransitionTokensAndTasks.remove(transition) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt new file mode 100644 index 000000000000..bf4a2abf9edc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode.education.data + +import android.content.Context +import android.util.Log +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.Serializer +import androidx.datastore.dataStore +import androidx.datastore.dataStoreFile +import com.android.framework.protobuf.InvalidProtocolBufferException +import com.android.internal.annotations.VisibleForTesting +import java.io.InputStream +import java.io.OutputStream +import kotlinx.coroutines.flow.first + +/** + * Manages interactions with the App Handle education datastore. + * + * This class provides a layer of abstraction between the UI/business logic and the underlying + * DataStore. + */ +class AppHandleEducationDatastoreRepository +@VisibleForTesting +constructor(private val dataStore: DataStore<WindowingEducationProto>) { + constructor( + context: Context + ) : this( + DataStoreFactory.create( + serializer = WindowingEducationProtoSerializer, + produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) })) + + /** + * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the + * DataStore is empty or there's an error reading, it returns the default value of Proto. + */ + suspend fun windowingEducationProto(): WindowingEducationProto = + try { + dataStore.data.first() + } catch (e: Exception) { + Log.e(TAG, "Unable to read from datastore") + WindowingEducationProto.getDefaultInstance() + } + + companion object { + private const val TAG = "AppHandleEducationDatastoreRepository" + private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb" + + object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> { + + override val defaultValue: WindowingEducationProto = + WindowingEducationProto.getDefaultInstance() + + override suspend fun readFrom(input: InputStream): WindowingEducationProto = + try { + WindowingEducationProto.parseFrom(input) + } catch (exception: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", exception) + } + + override suspend fun writeTo(windowingProto: WindowingEducationProto, output: OutputStream) = + windowingProto.writeTo(output) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto new file mode 100644 index 000000000000..d29ec53d9c61 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto @@ -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. + */ + +syntax = "proto2"; + +option java_package = "com.android.wm.shell.desktopmode.education.data"; +option java_multiple_files = true; + +// Desktop Windowing education data +message WindowingEducationProto { + // Timestamp in milliseconds of when the education was last viewed. + optional int64 education_viewed_timestamp_millis = 1; + // Timestamp in milliseconds of when the feature was last used. + optional int64 feature_used_timestamp_millis = 2; + oneof education_data { + // Fields specific to app handle education + AppHandleEducation app_handle_education = 3; + } + + message AppHandleEducation { + // Map that stores app launch count for corresponding package + map<string, int32> app_usage_stats = 1; + // Timestamp of when app_usage_stats was last cached + optional int64 app_usage_stats_last_update_timestamp_millis = 2; + } +}
\ No newline at end of file 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 723a53128bd0..428cc9118900 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 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, 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/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index c18964240f98..0d7f7f66032a 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.animation.ValueAnimator; 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 @@ public class PipMenuView extends FrameLayout { // 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 @@ public class PipMenuView extends FrameLayout { .getInteger(R.integer.config_pipExitAnimationDuration); initAccessibility(); + setContextForUser(); } private void initAccessibility() { @@ -476,7 +483,7 @@ public class PipMenuView extends FrameLayout { 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 @@ public class PipMenuView extends FrameLayout { 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 77743844f3c3..dc21f82c326c 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.graphics.Rect; 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.protolog.ProtoLog; 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 @@ import java.util.function.Consumer; */ 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 @@ public class PipController implements ConfigurationChangeListener, 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 @@ public class PipController implements ConfigurationChangeListener, @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 @@ public class PipController implements ConfigurationChangeListener, if (displayId != mPipDisplayLayoutState.getDisplayId()) { return; } - onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); } @Override @@ -284,10 +290,35 @@ public class PipController implements ConfigurationChangeListener, if (displayId != mPipDisplayLayoutState.getDisplayId()) { return; } - onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); + setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); } - private void onDisplayChanged(DisplayLayout layout) { + /** + * A callback for any observed transition that contains a display change in its + * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta. + */ + @Override + public void onDisplayChange(int displayId, int fromRotation, int toRotation, + @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) { + if (!mPipTransitionState.isInPip()) { + return; + } + + // Calculate the snap fraction pre-rotation. + float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()); + + // Update the caches to reflect the new display layout and movement bounds. + mPipDisplayLayoutState.rotateTo(toRotation); + mPipTouchHandler.updateMovementBounds(); + + // The policy is to keep PiP width, height and snap fraction invariant. + Rect toBounds = mPipBoundsState.getBounds(); + mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); + mPipBoundsState.setBounds(toBounds); + t.setBounds(mPipTransitionState.mPipTaskToken, toBounds); + } + + private void setDisplayLayout(DisplayLayout layout) { mPipDisplayLayoutState.setDisplayLayout(layout); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index d7c225b9e6e1..d75fa00b1fdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -1081,7 +1081,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha * Updates the current movement bounds based on whether the menu is currently visible and * resized. */ - private void updateMovementBounds() { + void updateMovementBounds() { Rect insetBounds = new Rect(); mPipBoundsAlgorithm.getInsetBounds(insetBounds); mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 497c3f704c82..f739d65e63c3 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 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { 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 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow"; private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen"; private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode"; + private static final String TAG_WM_COMPAT_UI = "CompatUi"; private static final boolean ENABLE_DEBUG = true; private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 48d17ec6963f..c11a112cde60 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 @@ class SplitScreenTransitions { 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 87dc16a79766..9bf515933b22 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 @@ -2243,6 +2243,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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. @@ -2258,7 +2259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " 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 @@ -2331,16 +2331,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // 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. @@ -2348,9 +2340,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering); + return out; } - return out; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d " + + "restoring to split", request.getDebugId()); + out = new WindowContainerTransaction(); + mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */); } + return out; } /** diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index a0408652a29b..4d761e18b990 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -44,6 +44,8 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "androidx.datastore_datastore", + "kotlinx_coroutines_test", "androidx.dynamicanimation_dynamicanimation", "dagger2", "frameworks-base-testutils", 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 3b858593bb28..7bb54498b877 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -731,6 +731,64 @@ class DesktopTasksControllerTest : ShellTestCase() { @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() @@ -1349,13 +1407,36 @@ class DesktopTasksControllerTest : ShellTestCase() { 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 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt new file mode 100644 index 000000000000..4d407387d323 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt @@ -0,0 +1,125 @@ +/* + * 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.education + +import android.content.Context +import android.testing.AndroidTestingRunner +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import androidx.test.core.app.ApplicationProvider +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository +import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto +import com.google.common.truth.Truth.assertThat +import java.io.File +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@ExperimentalCoroutinesApi +class AppHandleEducationDatastoreRepositoryTest { + private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext + private lateinit var testDatastore: DataStore<WindowingEducationProto> + private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository + private lateinit var datastoreScope: CoroutineScope + + @Before + fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + testDatastore = + DataStoreFactory.create( + serializer = + AppHandleEducationDatastoreRepository.Companion.WindowingEducationProtoSerializer, + scope = datastoreScope) { + testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE) + } + datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore) + } + + @After + fun tearDown() { + File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore") + .deleteRecursively() + + datastoreScope.cancel() + } + + @Test + fun getWindowingEducationProto_returnsCorrectProto() = + runTest(StandardTestDispatcher()) { + val windowingEducationProto = + createWindowingEducationProto( + educationViewedTimestampMillis = 123L, + featureUsedTimestampMillis = 124L, + appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2), + appUsageStatsLastUpdateTimestampMillis = 125L) + testDatastore.updateData { windowingEducationProto } + + val resultProto = datastoreRepository.windowingEducationProto() + + assertThat(resultProto).isEqualTo(windowingEducationProto) + } + + private fun createWindowingEducationProto( + educationViewedTimestampMillis: Long? = null, + featureUsedTimestampMillis: Long? = null, + appUsageStats: Map<String, Int>? = null, + appUsageStatsLastUpdateTimestampMillis: Long? = null + ): WindowingEducationProto = + WindowingEducationProto.newBuilder() + .apply { + if (educationViewedTimestampMillis != null) + setEducationViewedTimestampMillis(educationViewedTimestampMillis) + if (featureUsedTimestampMillis != null) + setFeatureUsedTimestampMillis(featureUsedTimestampMillis) + setAppHandleEducation( + createAppHandleEducationProto( + appUsageStats, appUsageStatsLastUpdateTimestampMillis)) + } + .build() + + private fun createAppHandleEducationProto( + appUsageStats: Map<String, Int>? = null, + appUsageStatsLastUpdateTimestampMillis: Long? = null + ): WindowingEducationProto.AppHandleEducation = + WindowingEducationProto.AppHandleEducation.newBuilder() + .apply { + if (appUsageStats != null) putAllAppUsageStats(appUsageStats) + if (appUsageStatsLastUpdateTimestampMillis != null) + setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestampMillis) + } + .build() + + companion object { + private const val GMAIL_PACKAGE_NAME = "com.google.android.gm" + private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb" + } +} 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 dd19d76d88cf..571bdd4ea32f 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.O 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 @@ class DesktopModeFlagsTest : ShellTestCase() { @JvmField @Rule val setFlagsRule = SetFlagsRule() - @Before - fun setUp() { - resetCache() + @After + fun tearDown() { + resetToggleOverrideCache() } // TODO(b/348193756): Add tests @@ -338,7 +338,7 @@ class DesktopModeFlagsTest : ShellTestCase() { } } - 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/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 6d68797b4430..fa905e2e5c37 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 @@ -57,6 +57,7 @@ import android.view.SurfaceView import android.view.View import android.view.WindowInsets.Type.statusBars import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession @@ -356,6 +357,36 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + fun testCloseButtonInFreeform() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) + val windowDecor = setUpMockDecorationForTask(task) + + onTaskOpening(task) + val onClickListenerCaptor = argumentCaptor<View.OnClickListener>() + verify(windowDecor).setCaptionListeners( + onClickListenerCaptor.capture(), any(), any(), any()) + + val onClickListener = onClickListenerCaptor.firstValue + val view = mock(View::class.java) + whenever(view.id).thenReturn(R.id.close_window) + + val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java) + desktopModeWindowDecorViewModel + .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter) + + onClickListener.onClick(view) + + val transactionCaptor = argumentCaptor<WindowContainerTransaction>() + verify(freeformTaskTransitionStarter).startRemoveTransition(transactionCaptor.capture()) + val wct = transactionCaptor.firstValue + + assertEquals(1, wct.getHierarchyOps().size) + assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK, + wct.getHierarchyOps().get(0).getType()) + assertEquals(task.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index d184f64b1c2c..1217b47664dd 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -42,6 +42,9 @@ constexpr bool hdr_10bit_plus() { constexpr bool initialize_gl_always() { return false; } +constexpr bool resample_gainmap_regions() { + return false; +} } // namespace hwui_flags #endif @@ -100,6 +103,7 @@ float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number bool Properties::clipSurfaceViews = false; bool Properties::hdr10bitPlus = false; +bool Properties::resampleGainmapRegions = false; int Properties::timeoutMultiplier = 1; @@ -175,6 +179,8 @@ bool Properties::load() { 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 e2646422030e..73e80ce4afd0 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -342,6 +342,7 @@ public: 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 cd3ae5342f4e..13c0b00daa21 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -97,3 +97,13 @@ flag { 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 ea5c14486ea4..6a65b8273194 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -87,8 +87,17 @@ public: 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 @@ public: // 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 @@ public: 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 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in 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 a88139d6b5d6..258bf91f2124 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 @@ bool HeapAllocator::allocPixelRef(SkBitmap* bitmap) { //////////////////////////////////////////////////////////////////////////////// -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 @@ bool RecyclingClippingPixelAllocator::allocPixelRef(SkBitmap* bitmap) { 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 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() { 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 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() { 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 b0a1074d6693..4b08f8dc7a93 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -216,8 +216,8 @@ private: */ 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 @@ public: 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/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 95945d77730d..f16fa801a3e6 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -118,3 +118,10 @@ flag { 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/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt index e2ab31662380..c61a2ac9f0dd 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt @@ -16,7 +16,9 @@ package com.android.packageinstaller.v2.ui -import android.app.Activity +import android.app.Activity.RESULT_CANCELED +import android.app.Activity.RESULT_FIRST_USER +import android.app.Activity.RESULT_OK import android.app.AppOpsManager import android.content.ActivityNotFoundException import android.content.Intent @@ -135,7 +137,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { } InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted) - else -> setResult(Activity.RESULT_CANCELED, null, true) + else -> setResult(RESULT_CANCELED, null, true) } } @@ -169,7 +171,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { val success = installStage as InstallSuccess if (success.shouldReturnResult) { val successIntent = success.resultIntent - setResult(Activity.RESULT_OK, successIntent, true) + setResult(RESULT_OK, successIntent, true) } else { val successDialog = InstallSuccessFragment(success) showDialogInner(successDialog) @@ -180,7 +182,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { val failed = installStage as InstallFailed if (failed.shouldReturnResult) { val failureIntent = failed.resultIntent - setResult(Activity.RESULT_FIRST_USER, failureIntent, true) + setResult(RESULT_FIRST_USER, failureIntent, true) } else { val failureDialog = InstallFailedFragment(failed) showDialogInner(failureDialog) @@ -219,7 +221,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { shouldFinish = blockedByPolicyDialog == null showDialogInner(blockedByPolicyDialog) } - setResult(Activity.RESULT_CANCELED, null, shouldFinish) + setResult(RESULT_CANCELED, null, shouldFinish) } /** @@ -257,6 +259,10 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) { super.setResult(resultCode, data) + if (resultCode != RESULT_OK) { + // Let callers know that the install was cancelled + installViewModel!!.cleanupInstall() + } if (shouldFinish) { finish() } @@ -282,7 +288,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) { installViewModel!!.cleanupInstall() } - setResult(Activity.RESULT_CANCELED, null, true) + setResult(RESULT_CANCELED, null, true) } override fun onNegativeResponse(resultCode: Int, data: Intent?) { @@ -318,7 +324,7 @@ class InstallLaunch : FragmentActivity(), InstallActionListener { if (localLogv) { Log.d(LOG_TAG, "Opening $intent") } - setResult(Activity.RESULT_OK, intent, true) + setResult(RESULT_OK, intent, true) if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) { startActivity(intent) } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index 7124ed2d96b8..c6eb9fddf2a7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -178,7 +178,7 @@ public class CsipDeviceManager { } log("updateRelationshipOfGroupDevices: mCachedDevices list =" + mCachedDevices.toString()); - // Get the preferred main device by getPreferredMainDeviceWithoutConectionState + // Get the preferred main device by getPreferredMainDeviceWithoutConnectionState List<CachedBluetoothDevice> groupDevicesList = getGroupDevicesFromAllOfDevicesList(groupId); CachedBluetoothDevice preferredMainDevice = getPreferredMainDevice(groupId, groupDevicesList); @@ -373,6 +373,7 @@ public class CsipDeviceManager { preferredMainDevice.addMemberDevice(deviceItem); mCachedDevices.remove(deviceItem); mBtManager.getEventManager().dispatchDeviceRemoved(deviceItem); + preferredMainDevice.refresh(); hasChanged = true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 27fcdbe0334f..26905b1d86d2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -80,6 +80,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE"; public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE"; public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE"; + public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING"; public static final int BROADCAST_STATE_UNKNOWN = 0; public static final int BROADCAST_STATE_ON = 1; public static final int BROADCAST_STATE_OFF = 2; 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 2c982d6a9fe2..f7492cfc9a72 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java @@ -40,18 +40,17 @@ public class TestModeBuilder { private ZenModeConfig.ZenRule mConfigZenRule; public static final ZenMode EXAMPLE = new TestModeBuilder().build(); - public static final ZenMode MANUAL_DND_ACTIVE = ZenMode.manualDndMode( - new AutomaticZenRule.Builder("Do Not Disturb", Uri.parse("rule://dnd")) - .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY) - .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build()) - .build(), - /* isActive= */ true); - public static final ZenMode MANUAL_DND_INACTIVE = ZenMode.manualDndMode( - new AutomaticZenRule.Builder("Do Not Disturb", Uri.parse("rule://dnd")) - .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY) - .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build()) - .build(), - /* isActive= */ false); + 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 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 88497a393ce8..2f4b2efeec7f 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_ALL; 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; @@ -188,11 +189,37 @@ public class ZenMode implements Parcelable { 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 f533c951d7f8..64e503b323b8 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 @@ public class ZenModesBackend { 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 @@ public class ZenModesBackend { .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) { @@ -175,7 +174,6 @@ public class ZenModesBackend { mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG, /* fromUser= */ true); } else { - // TODO: b/333527800 - This should (potentially) snooze the rule if it was active. mNotificationManager.setAutomaticZenRuleState(mode.getId(), new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE, Condition.SOURCE_USER_ACTION)); diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java index 69c7410818dd..6198d80cefe6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java @@ -181,14 +181,14 @@ public class CreateUserDialogController { * admin status. */ public Dialog createDialog(Activity activity, - ActivityStarter activityStarter, boolean isMultipleAdminEnabled, + ActivityStarter activityStarter, boolean canCreateAdminUser, NewUserData successCallback, Runnable cancelCallback) { mActivity = activity; mCustomDialogHelper = new CustomDialogHelper(activity); mSuccessCallback = successCallback; mCancelCallback = cancelCallback; mActivityStarter = activityStarter; - addCustomViews(isMultipleAdminEnabled); + addCustomViews(canCreateAdminUser); mUserCreationDialog = mCustomDialogHelper.getDialog(); updateLayout(); mUserCreationDialog.setOnDismissListener(view -> finish()); @@ -197,19 +197,19 @@ public class CreateUserDialogController { return mUserCreationDialog; } - private void addCustomViews(boolean isMultipleAdminEnabled) { + private void addCustomViews(boolean canCreateAdminUser) { addGrantAdminView(); addUserInfoEditView(); mCustomDialogHelper.setPositiveButton(R.string.next, view -> { mCurrentState++; - if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) { + if (mCurrentState == GRANT_ADMIN_DIALOG && !canCreateAdminUser) { mCurrentState++; } updateLayout(); }); mCustomDialogHelper.setNegativeButton(R.string.back, view -> { mCurrentState--; - if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) { + if (mCurrentState == GRANT_ADMIN_DIALOG && !canCreateAdminUser) { mCurrentState--; } updateLayout(); 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 f94f21fe5d45..698eb8159846 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 @@ -19,7 +19,9 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; @@ -352,4 +354,34 @@ public class CsipDeviceManagerTest { assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3); assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice); } + + @Test + public void onProfileConnectionStateChangedIfProcessed_addMemberDevice_refreshUI() { + mCachedDevice3.setGroupId(GROUP1); + + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3, + BluetoothProfile.STATE_CONNECTED); + + verify(mCachedDevice1).refresh(); + } + + @Test + public void onProfileConnectionStateChangedIfProcessed_switchMainDevice_refreshUI() { + when(mDevice3.isConnected()).thenReturn(true); + when(mDevice2.isConnected()).thenReturn(false); + when(mDevice1.isConnected()).thenReturn(false); + mCachedDevice3.setGroupId(GROUP1); + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3, + BluetoothProfile.STATE_CONNECTED); + + when(mDevice3.isConnected()).thenReturn(false); + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice3, + BluetoothProfile.STATE_DISCONNECTED); + when(mDevice1.isConnected()).thenReturn(true); + mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, + BluetoothProfile.STATE_CONNECTED); + + verify(mCachedDevice3).switchMemberDeviceContent(mCachedDevice1); + verify(mCachedDevice3, atLeastOnce()).refresh(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java index 00c7ae3e97d7..539519b3ec3b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java @@ -123,7 +123,6 @@ public class ZenModesBackendTest { zenRule.id = id; zenRule.pkg = "package"; zenRule.enabled = azr.isEnabled(); - zenRule.snoozing = false; zenRule.conditionId = azr.getConditionId(); zenRule.condition = new Condition(azr.getConditionId(), "", active ? Condition.STATE_TRUE : Condition.STATE_FALSE, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 03c2a83519d8..65937ea067c6 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 @@ public class GlobalSettingsValidators { 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 @@ public class GlobalSettingsValidators { 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/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index e66bacf40576..d39b5645109d 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -629,11 +629,11 @@ public class SettingsBackupTest { 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( 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 66a2fae0c4c3..c698d18bfde8 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 android.util.Log; import com.android.systemui.accessibility.accessibilitymenu.R; -import java.util.HashMap; import java.util.Map; /** @@ -52,80 +51,80 @@ public class A11yMenuShortcut { private static final int LABEL_TEXT_INDEX = 3; /** Map stores all shortcut resource IDs that is in matching order of defined shortcut. */ - private static final Map<ShortcutId, int[]> sShortcutResource = new HashMap<>() {{ - put(ShortcutId.ID_ASSISTANT_VALUE, new int[] { + private static final Map<ShortcutId, int[]> sShortcutResource = Map.ofEntries( + Map.entry(ShortcutId.ID_ASSISTANT_VALUE, new int[] { R.drawable.ic_logo_a11y_assistant_24dp, R.color.assistant_color, R.string.assistant_utterance, R.string.assistant_label, - }); - put(ShortcutId.ID_A11YSETTING_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_A11YSETTING_VALUE, new int[] { R.drawable.ic_logo_a11y_settings_24dp, R.color.a11y_settings_color, R.string.a11y_settings_label, R.string.a11y_settings_label, - }); - put(ShortcutId.ID_POWER_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_POWER_VALUE, new int[] { R.drawable.ic_logo_a11y_power_24dp, R.color.power_color, R.string.power_utterance, R.string.power_label, - }); - put(ShortcutId.ID_RECENT_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_RECENT_VALUE, new int[] { R.drawable.ic_logo_a11y_recent_apps_24dp, R.color.recent_apps_color, R.string.recent_apps_label, R.string.recent_apps_label, - }); - put(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_LOCKSCREEN_VALUE, new int[] { R.drawable.ic_logo_a11y_lock_24dp, R.color.lockscreen_color, R.string.lockscreen_label, R.string.lockscreen_label, - }); - put(ShortcutId.ID_QUICKSETTING_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_QUICKSETTING_VALUE, new int[] { R.drawable.ic_logo_a11y_quick_settings_24dp, R.color.quick_settings_color, R.string.quick_settings_label, R.string.quick_settings_label, - }); - put(ShortcutId.ID_NOTIFICATION_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_NOTIFICATION_VALUE, new int[] { R.drawable.ic_logo_a11y_notifications_24dp, R.color.notifications_color, R.string.notifications_label, R.string.notifications_label, - }); - put(ShortcutId.ID_SCREENSHOT_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_SCREENSHOT_VALUE, new int[] { R.drawable.ic_logo_a11y_screenshot_24dp, R.color.screenshot_color, R.string.screenshot_utterance, R.string.screenshot_label, - }); - put(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_BRIGHTNESS_UP_VALUE, new int[] { R.drawable.ic_logo_a11y_brightness_up_24dp, R.color.brightness_color, R.string.brightness_up_label, R.string.brightness_up_label, - }); - put(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE, new int[] { R.drawable.ic_logo_a11y_brightness_down_24dp, R.color.brightness_color, R.string.brightness_down_label, R.string.brightness_down_label, - }); - put(ShortcutId.ID_VOLUME_UP_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_VOLUME_UP_VALUE, new int[] { R.drawable.ic_logo_a11y_volume_up_24dp, R.color.volume_color, R.string.volume_up_label, R.string.volume_up_label, - }); - put(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] { + }), + Map.entry(ShortcutId.ID_VOLUME_DOWN_VALUE, new int[] { R.drawable.ic_logo_a11y_volume_down_24dp, R.color.volume_color, R.string.volume_down_label, R.string.volume_down_label, - }); - }}; + }) + ); /** Shortcut id used to identify. */ private int mShortcutId = ShortcutId.UNSPECIFIED_ID_VALUE.ordinal(); diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 19dced526692..9046d4e328f4 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -156,17 +156,6 @@ flag { } flag { - name: "pss_app_selector_abrupt_exit_fix" - namespace: "systemui" - description: "Fixes the app selector abruptly disappearing without an animation, when the" - "selected task is the foreground task." - bug: "314385883" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" @@ -1197,6 +1186,17 @@ flag { } flag { + name: "hubmode_fullscreen_vertical_swipe_fix" + namespace: "systemui" + description: "Bug fix that enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade" + bug: "340177049" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + +flag { namespace: "systemui" name: "remove_update_listener_in_qs_icon_view_impl" description: "Remove update listeners in QsIconViewImpl class to avoid memory leak." diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt new file mode 100644 index 000000000000..8f5cdbf0b2bc --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetector.kt @@ -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.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 +import org.jetbrains.uast.UClass +import org.jetbrains.uast.getParentOfType + +/** + * Checks if registerContentObserver/registerContentObserverAsUser/unregisterContentObserver is + * called on a ContentResolver (or subclasses), and directs the caller to using + * com.android.systemui.util.settings.SettingsProxy or its sub-classes. + */ +@Suppress("UnstableApiUsage") +class RegisterContentObserverViaContentResolverDetector : Detector(), SourceCodeScanner { + + override fun getApplicableMethodNames(): List<String> { + return CONTENT_RESOLVER_METHOD_LIST + } + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + val classQualifiedName = node.getParentOfType(UClass::class.java)?.qualifiedName + if (classQualifiedName in CLASSNAME_ALLOWLIST) { + // Don't warn for class we want the developers to use. + return + } + + val evaluator = context.evaluator + if (evaluator.isMemberInSubClassOf(method, "android.content.ContentResolver")) { + context.report( + issue = CONTENT_RESOLVER_ERROR, + location = context.getNameLocation(node), + message = + "`ContentResolver.${method.name}()` should be replaced with " + + "an appropriate interface API call, for eg. " + + "`<SettingsProxy>/<UserSettingsProxy>.${method.name}()`" + ) + } + } + + companion object { + @JvmField + val CONTENT_RESOLVER_ERROR: Issue = + Issue.create( + id = "RegisterContentObserverViaContentResolver", + briefDescription = + "Content observer registration done via `ContentResolver`" + + "instead of `SettingsProxy or child interfaces.`", + // lint trims indents and converts \ to line continuations + explanation = + """ + Use registerContentObserver/unregisterContentObserver methods in \ + `SettingsProxy`, `UserSettingsProxy` or `GlobalSettings` class instead of \ + using `ContentResolver.registerContentObserver` or \ + `ContentResolver.unregisterContentObserver`.""", + category = Category.PERFORMANCE, + priority = 10, + severity = Severity.ERROR, + implementation = + Implementation( + RegisterContentObserverViaContentResolverDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + private val CLASSNAME_ALLOWLIST = + listOf( + "com.android.systemui.util.settings.SettingsProxy", + "com.android.systemui.util.settings.UserSettingsProxy", + "com.android.systemui.util.settings.GlobalSettings", + "com.android.systemui.util.settings.SecureSettings", + "com.android.systemui.util.settings.SystemSettings" + ) + + private val CONTENT_RESOLVER_METHOD_LIST = + listOf( + "registerContentObserver", + "registerContentObserverAsUser", + "unregisterContentObserver" + ) + } +} 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 5206b05a3f4e..a1f4f5507e5f 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,7 +46,8 @@ class SystemUIIssueRegistry : IssueRegistry() { DemotingTestWithoutBugDetector.ISSUE, TestFunctionNameViolationDetector.ISSUE, MissingApacheLicenseDetector.ISSUE, - RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING + RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING, + RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR ) override val api: Int diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt new file mode 100644 index 000000000000..1d33bce8dea8 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterContentObserverViaContentResolverDetectorTest.kt @@ -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.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 + +class RegisterContentObserverViaContentResolverDetectorTest : SystemUILintDetectorTest() { + + override fun getDetector(): Detector = RegisterContentObserverViaContentResolverDetector() + + override fun getIssues(): List<Issue> = + listOf(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + + @Test + fun testRegisterContentObserver_throwError() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void register(Context context) { + context.getContentResolver(). + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Error: ContentResolver.registerContentObserver() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.registerContentObserver() [RegisterContentObserverViaContentResolver] + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + ~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testRegisterContentObserverForUser_throwError() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void register(Context context) { + context.getContentResolver(). + registerContentObserverAsUser(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Error: ContentResolver.registerContentObserverAsUser() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.registerContentObserverAsUser() [RegisterContentObserverViaContentResolver] + registerContentObserverAsUser(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testSuppressRegisterContentObserver() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + @SuppressWarnings("RegisterContentObserverViaContentResolver") + public void register(Context context) { + context.getContentResolver(). + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expectClean() + } + + @Test + fun testRegisterContentObserverInSettingsProxy_allowed() { + lint() + .files( + TestFiles.java( + """ + package com.android.systemui.util.settings; + import android.content.Context; + + public class SettingsProxy { + public void register(Context context) { + context.getContentResolver(). + registerContentObserver(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expectClean() + } + + @Test + fun testNoopIfNoCall() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class SettingsProxy { + public void register(Context context) { + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expectClean() + } + + @Test + fun testUnRegisterContentObserver_throwError() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + + public class TestClass { + public void register(Context context) { + context.getContentResolver(). + unregisterContentObserver(mSettingObserver); + } + } + """ + ) + .indented(), + *androidStubs + ) + .issues(RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Error: ContentResolver.unregisterContentObserver() should be replaced with an appropriate interface API call, for eg. <SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver() [RegisterContentObserverViaContentResolver] + unregisterContentObserver(mSettingObserver); + ~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } +} 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 69f117431663..b65b47123eaa 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.modifiers.thenIf 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 @@ fun CommunalHub( } } // 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/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt new file mode 100644 index 000000000000..04bcc3624532 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt @@ -0,0 +1,198 @@ +/* + * 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.ui.composable + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +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.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.modifiers.background +import com.android.compose.modifiers.height +import com.android.compose.modifiers.width +import com.android.systemui.deviceentry.shared.model.BiometricMessage +import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder +import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay +import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.ui.binder.AlternateBouncerUdfpsViewBinder +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel +import com.android.systemui.res.R +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@Composable +fun AlternateBouncer( + alternateBouncerDependencies: AlternateBouncerDependencies, + modifier: Modifier = Modifier, +) { + + val isVisible by + alternateBouncerDependencies.viewModel.isVisible.collectAsStateWithLifecycle( + initialValue = false + ) + + val udfpsIconLocation by + alternateBouncerDependencies.udfpsIconViewModel.iconLocation.collectAsStateWithLifecycle( + initialValue = null + ) + + // TODO (b/353955910): back handling doesn't work + BackHandler { alternateBouncerDependencies.viewModel.onBackRequested() } + + AnimatedVisibility( + visible = isVisible, + enter = fadeIn(), + exit = fadeOut(), + modifier = modifier, + ) { + Box( + contentAlignment = Alignment.TopCenter, + modifier = + Modifier.background(color = Colors.AlternateBouncerBackgroundColor, alpha = { 1f }) + .pointerInput(Unit) { + detectTapGestures( + onTap = { alternateBouncerDependencies.viewModel.onTapped() } + ) + }, + ) { + StatusMessage( + viewModel = alternateBouncerDependencies.messageAreaViewModel, + ) + } + + udfpsIconLocation?.let { udfpsLocation -> + Box { + DeviceEntryIcon( + viewModel = alternateBouncerDependencies.udfpsIconViewModel, + modifier = + Modifier.width { udfpsLocation.width } + .height { udfpsLocation.height } + .fillMaxHeight() + .offset { + IntOffset( + x = udfpsLocation.left, + y = udfpsLocation.top, + ) + }, + ) + } + + UdfpsA11yOverlay( + viewModel = alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel.get(), + modifier = Modifier.fillMaxHeight(), + ) + } + } +} + +@ExperimentalCoroutinesApi +@Composable +private fun StatusMessage( + viewModel: AlternateBouncerMessageAreaViewModel, + modifier: Modifier = Modifier, +) { + val message: BiometricMessage? by + viewModel.message.collectAsStateWithLifecycle(initialValue = null) + + Crossfade( + targetState = message, + label = "Alternate Bouncer message", + animationSpec = tween(), + modifier = modifier, + ) { biometricMessage -> + biometricMessage?.let { + Text( + textAlign = TextAlign.Center, + text = it.message ?: "", + color = Colors.AlternateBouncerTextColor, + fontSize = 18.sp, + lineHeight = 24.sp, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 92.dp), + ) + } + } +} + +@ExperimentalCoroutinesApi +@Composable +private fun DeviceEntryIcon( + viewModel: AlternateBouncerUdfpsIconViewModel, + modifier: Modifier = Modifier, +) { + AndroidView( + modifier = modifier, + factory = { context -> + val view = + DeviceEntryIconView(context, null).apply { + id = R.id.alternate_bouncer_udfps_icon_view + contentDescription = + context.resources.getString(R.string.accessibility_fingerprint_label) + } + AlternateBouncerUdfpsViewBinder.bind(view, viewModel) + view + }, + ) +} + +/** TODO (b/353955910): Validate accessibility CUJs */ +@ExperimentalCoroutinesApi +@Composable +private fun UdfpsA11yOverlay( + viewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel, + modifier: Modifier = Modifier, +) { + AndroidView( + factory = { context -> + val view = + UdfpsAccessibilityOverlay(context).apply { + id = R.id.alternate_bouncer_udfps_accessibility_overlay + } + UdfpsAccessibilityOverlayBinder.bind(view, viewModel) + view + }, + modifier = modifier, + ) +} + +private object Colors { + val AlternateBouncerBackgroundColor: Color = Color.Black.copy(alpha = .66f) + val AlternateBouncerTextColor: Color = Color.White +} 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 9afb4d5b7523..a78c038595f1 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 @@ import dagger.Module [ CommunalBlueprintModule::class, OptionalSectionModule::class, - ShortcutsBesideUdfpsBlueprintModule::class, ], ) interface LockscreenSceneBlueprintModule 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 a5e120c6f04e..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ /dev/null @@ -1,259 +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 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( - 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, - 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( - smartSpacePaddingTop = viewModel::getSmartSpacePaddingTop, - modifier = - Modifier.graphicsLayer { - translationX = unfoldTranslations.start - }, - ) - } - if (isShadeLayoutWide) { - with(notificationSection) { - Notifications( - areNotificationsVisible = areNotificationsVisible, - isShadeLayoutWide = isShadeLayoutWide, - burnInParams = null, - modifier = - Modifier.fillMaxWidth(0.5f) - .fillMaxHeight() - .align(alignment = Alignment.TopEnd) - ) - } - } - } - if (!isShadeLayoutWide) { - with(notificationSection) { - Notifications( - areNotificationsVisible = areNotificationsVisible, - isShadeLayoutWide = isShadeLayoutWide, - 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 9c72d933da32..364adcaffd77 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.compose.ui.viewinterop.AndroidView 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.view.KeyguardIndicationArea 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 @@ class BottomAreaSection @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 @@ constructor( 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 @@ constructor( @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 @@ constructor( } 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/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 84782fdfc0af..0bef05dc00ba 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 @@ fun SceneScope.NotificationScrollingStack( shouldReserveSpaceForNavBar: Boolean = true, shouldIncludeHeadsUpSpace: Boolean = true, shadeMode: ShadeMode, + onEmptySpaceClick: (() -> Unit)? = null, modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() @@ -328,8 +329,6 @@ fun SceneScope.NotificationScrollingStack( // 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 @@ fun SceneScope.NotificationScrollingStack( ) ) } - .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 7159def8d60a..66be7bc83c64 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.Column 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 @@ import kotlinx.coroutines.flow.Flow 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 @@ constructor( 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 @@ constructor( 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 cdcd840b2f34..8bba0f4f3651 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.LocalDisplayCutout 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 @@ private fun SceneScope.QuickSettingsScene( ) { 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 @@ private fun SceneScope.QuickSettingsScene( fadeOut(tween(customizingAnimationDuration)), ) { ExpandedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, + viewModelFactory = viewModel.shadeHeaderViewModelFactory, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, @@ -347,7 +352,7 @@ private fun SceneScope.QuickSettingsScene( } else -> CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, + viewModelFactory = viewModel.shadeHeaderViewModelFactory, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, @@ -417,7 +422,7 @@ private fun SceneScope.QuickSettingsScene( ) 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 f6d1283e1f29..eea00c4f2935 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.battery.BatteryMeterViewController 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 @@ import kotlinx.coroutines.flow.Flow 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 @@ constructor( 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/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index facbcaffcb5a..445ffcb0c60c 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.LowestZIndexContentPicker 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 @@ import java.util.Optional /** 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 1cd48bf2e628..8c53740ebfd0 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.CutoutLocation 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 @@ object ShadeHeader { @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 @@ fun SceneScope.CollapsedShadeHeader( @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 77b48d3d307e..0e3fcf4598af 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.LocalDisplayCutout 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.session.ui.composable.SaveableSession 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 @@ class ShadeScene 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 @@ constructor( 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 @@ constructor( ) = ShadeScene( notificationStackScrollView.get(), - viewModel = viewModel, + viewModel = rememberViewModel { contentViewModelFactory.create() }, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, @@ -196,7 +203,7 @@ constructor( @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 @@ private fun SceneScope.ShadeScene( @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 @@ private fun SceneScope.SingleShade( 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 @@ private fun SceneScope.SingleShade( 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 @@ private fun SceneScope.SingleShade( }, ) { CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, + viewModelFactory = viewModel.shadeHeaderViewModelFactory, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, @@ -361,6 +368,8 @@ private fun SceneScope.SingleShade( maxScrimTop = { maxNotifScrimTop.value }, shadeMode = ShadeMode.Single, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, + onEmptySpaceClick = + viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable }, ) }, ) @@ -407,7 +416,7 @@ private fun SceneScope.SingleShade( @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 @@ private fun SceneScope.SplitShade( } } - 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 @@ private fun SceneScope.SplitShade( 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 @@ private fun SceneScope.SplitShade( modifier = Modifier.fillMaxSize(), ) { CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, + viewModelFactory = viewModel.shadeHeaderViewModelFactory, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, @@ -522,7 +534,7 @@ private fun SceneScope.SplitShade( .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 @@ private fun SceneScope.SplitShade( 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/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt index 9da2a1b06f30..5ffb6f82fbba 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt @@ -50,7 +50,7 @@ fun VolumePanelRoot( ) { val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() - val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null) + val components by viewModel.componentsLayout.collectAsStateWithLifecycle() with(VolumePanelComposeScope(state)) { components?.let { componentsState -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt new file mode 100644 index 000000000000..5eabd2275285 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt @@ -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.compose.animation.scene + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.SpringSpec +import com.android.compose.animation.scene.content.state.ContentState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +internal fun CoroutineScope.animateContent( + transition: ContentState.Transition<*>, + oneOffAnimation: OneOffAnimation, + targetProgress: Float, + startTransition: () -> Unit, + finishTransition: () -> Unit, +) { + // Start the transition. This will compute the TransformationSpec associated to [transition], + // which we need to initialize the Animatable that will actually animate it. + startTransition() + + // The transition now contains the transformation spec that we should use to instantiate the + // Animatable. + val animationSpec = transition.transformationSpec.progressSpec + val visibilityThreshold = + (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold + val replacedTransition = transition.replacedTransition + val initialProgress = replacedTransition?.progress ?: 0f + val initialVelocity = replacedTransition?.progressVelocity ?: 0f + val animatable = + Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also { + oneOffAnimation.animatable = it + } + + // Animate the progress to its target value. + // + // Important: We start atomically to make sure that we start the coroutine even if it is + // cancelled right after it is launched, so that finishTransition() is correctly called. + // Otherwise, this transition will never be stopped and we will never settle to Idle. + oneOffAnimation.job = + launch(start = CoroutineStart.ATOMIC) { + try { + animatable.animateTo(targetProgress, animationSpec, initialVelocity) + } finally { + finishTransition() + } + } +} + +internal class OneOffAnimation { + /** + * The animatable used to animate this transition. + * + * Note: This is lateinit because we need to first create this object so that + * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to + * the transition, which is needed to initialize this Animatable. + */ + lateinit var animatable: Animatable<Float, AnimationVector1D> + + /** The job that is animating [animatable]. */ + lateinit var job: Job + + val progress: Float + get() = animatable.value + + val progressVelocity: Float + get() = animatable.velocity + + fun finish(): Job = job +} + +// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size +// and screen density. +internal const val ProgressVisibilityThreshold = 1e-3f diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 1fc1f989b095..68a6c9836875 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -16,15 +16,10 @@ package com.android.compose.animation.scene -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.AnimationVector1D -import androidx.compose.animation.core.SpringSpec import com.android.compose.animation.scene.content.state.TransitionState import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job -import kotlinx.coroutines.launch /** * Transition to [target] using a canned animation. This function will try to be smart and take over @@ -50,7 +45,7 @@ internal fun CoroutineScope.animateToScene( return when (transitionState) { is TransitionState.Idle -> { - animate( + animateToScene( layoutState, target, transitionKey, @@ -80,13 +75,11 @@ internal fun CoroutineScope.animateToScene( } else { // The transition is in progress: start the canned animation at the same // progress as it was in. - animate( + animateToScene( layoutState, target, transitionKey, isInitiatedByUserInput, - initialProgress = progress, - initialVelocity = transitionState.progressVelocity, replacedTransition = transitionState, ) } @@ -102,13 +95,11 @@ internal fun CoroutineScope.animateToScene( layoutState.finishTransition(transitionState, target) null } else { - animate( + animateToScene( layoutState, target, transitionKey, isInitiatedByUserInput, - initialProgress = progress, - initialVelocity = transitionState.progressVelocity, reversed = true, replacedTransition = transitionState, ) @@ -140,7 +131,7 @@ internal fun CoroutineScope.animateToScene( animateToScene(layoutState, animateFrom, transitionKey = null) } - animate( + animateToScene( layoutState, target, transitionKey, @@ -154,103 +145,68 @@ internal fun CoroutineScope.animateToScene( } } -private fun CoroutineScope.animate( +private fun CoroutineScope.animateToScene( layoutState: MutableSceneTransitionLayoutStateImpl, targetScene: SceneKey, transitionKey: TransitionKey?, isInitiatedByUserInput: Boolean, replacedTransition: TransitionState.Transition?, - initialProgress: Float = 0f, - initialVelocity: Float = 0f, reversed: Boolean = false, fromScene: SceneKey = layoutState.transitionState.currentScene, chain: Boolean = true, ): TransitionState.Transition { + val oneOffAnimation = OneOffAnimation() val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { - OneOffTransition( + OneOffSceneTransition( key = transitionKey, fromScene = targetScene, toScene = fromScene, currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = false, replacedTransition = replacedTransition, + oneOffAnimation = oneOffAnimation, ) } else { - OneOffTransition( + OneOffSceneTransition( key = transitionKey, fromScene = fromScene, toScene = targetScene, currentScene = targetScene, isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = false, replacedTransition = replacedTransition, + oneOffAnimation = oneOffAnimation, ) } - // Change the current layout state to start this new transition. This will compute the - // TransformationSpec associated to this transition, which we need to initialize the Animatable - // that will actually animate it. - layoutState.startTransition(transition, chain) - - // The transition now contains the transformation spec that we should use to instantiate the - // Animatable. - val animationSpec = transition.transformationSpec.progressSpec - val visibilityThreshold = - (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold - val animatable = - Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also { - transition.animatable = it - } - - // Animate the progress to its target value. - // Important: We start atomically to make sure that we start the coroutine even if it is - // cancelled right after it is launched, so that finishTransition() is correctly called. - // Otherwise, this transition will never be stopped and we will never settle to Idle. - transition.job = - launch(start = CoroutineStart.ATOMIC) { - try { - animatable.animateTo(targetProgress, animationSpec, initialVelocity) - } finally { - layoutState.finishTransition(transition, targetScene) - } - } + animateContent( + transition = transition, + oneOffAnimation = oneOffAnimation, + targetProgress = targetProgress, + startTransition = { layoutState.startTransition(transition, chain) }, + finishTransition = { layoutState.finishTransition(transition, targetScene) }, + ) return transition } -private class OneOffTransition( +private class OneOffSceneTransition( override val key: TransitionKey?, fromScene: SceneKey, toScene: SceneKey, override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, - override val isUserInputOngoing: Boolean, replacedTransition: TransitionState.Transition?, + private val oneOffAnimation: OneOffAnimation, ) : TransitionState.Transition(fromScene, toScene, replacedTransition) { - /** - * The animatable used to animate this transition. - * - * Note: This is lateinit because we need to first create this Transition object so that - * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to - * it, which is need to initialize this Animatable. - */ - lateinit var animatable: Animatable<Float, AnimationVector1D> - - /** The job that is animating [animatable]. */ - lateinit var job: Job - override val progress: Float - get() = animatable.value + get() = oneOffAnimation.progress override val progressVelocity: Float - get() = animatable.velocity + get() = oneOffAnimation.progressVelocity - override fun finish(): Job = job -} + override val isUserInputOngoing: Boolean = false -// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size -// and screen density. -internal const val ProgressVisibilityThreshold = 1e-3f + override fun finish(): Job = oneOffAnimation.finish() +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt index 0105af3377fd..fe16ef75118b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt @@ -127,16 +127,7 @@ private class PredictiveBackTransition( return coroutineScope .launch(start = CoroutineStart.ATOMIC) { try { - if (currentScene == toScene) { - animatable.animateTo(targetProgress, transformationSpec.progressSpec) - } else { - // If the back gesture is cancelled, the progress is animated back to 0f by - // the system. But we need this animate call anyways because - // PredictiveBackHandler doesn't guarantee that it ends at 0f. Since the - // remaining change in progress is usually very small, the progressSpec is - // omitted and the default spring spec used instead. - animatable.animateTo(targetProgress) - } + animatable.animateTo(targetProgress) } finally { state.finishTransition(this@PredictiveBackTransition, scene) } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt index c414fbe1c2db..0eaecb09e97e 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt @@ -18,8 +18,6 @@ package com.android.compose.animation.scene import androidx.activity.BackEventCompat import androidx.activity.ComponentActivity -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.rememberCoroutineScope @@ -61,23 +59,7 @@ class PredictiveBackHandlerTest { @Test fun testPredictiveBack() { - val transitionFrames = 2 - val layoutState = - rule.runOnUiThread { - MutableSceneTransitionLayoutState( - SceneA, - transitions = - transitions { - from(SceneA, to = SceneB) { - spec = - tween( - durationMillis = transitionFrames * 16, - easing = LinearEasing - ) - } - } - ) - } + val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } rule.setContent { SceneTransitionLayout(layoutState) { scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) } @@ -106,27 +88,12 @@ class PredictiveBackHandlerTest { assertThat(layoutState.transitionState).hasCurrentScene(SceneA) assertThat(layoutState.transitionState).isIdle() - rule.mainClock.autoAdvance = false - // Start again and commit it. rule.runOnUiThread { dispatcher.dispatchOnBackStarted(backEvent()) dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f)) dispatcher.onBackPressed() } - rule.mainClock.advanceTimeByFrame() - rule.waitForIdle() - val transition2 = assertThat(layoutState.transitionState).isTransition() - // verify that transition picks up progress from preview - assertThat(transition2).hasProgress(0.4f, tolerance = 0.0001f) - - rule.mainClock.advanceTimeByFrame() - rule.waitForIdle() - // verify that transition is half way between preview-end-state (0.4f) and target-state (1f) - // after one frame - assertThat(transition2).hasProgress(0.7f, tolerance = 0.0001f) - - rule.mainClock.autoAdvance = true rule.waitForIdle() assertThat(layoutState.transitionState).hasCurrentScene(SceneB) assertThat(layoutState.transitionState).isIdle() diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml index b4c839f08607..7577147a6f16 100644 --- a/packages/SystemUI/lint-baseline.xml +++ b/packages/SystemUI/lint-baseline.xml @@ -32157,4 +32157,631 @@ column="6"/> </issue> + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt" + line="154" + column="33"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java" + line="164" + column="22"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt" + line="61" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" awaitClose { resolver.unregisterContentObserver(observer) }" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/util/animation/data/repository/AnimationStatusRepository.kt" + line="67" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mSettingObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="123" + column="38"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mSettingObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="180" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="211" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java" + line="219" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="46" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="48" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" resolver.unregisterContentObserver(allowedObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="54" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" resolver.unregisterContentObserver(onObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt" + line="55" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(mQuickPickupGesture, false, this," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java" + line="511" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java" + line="513" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(mAlwaysOnEnabled, false, this," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java" + line="514" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="2470" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3077" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3168" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3957" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java" + line="3961" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt" + line="486" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt" + line="492" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" contentResolver.unregisterContentObserver(settingsObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt" + line="543" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt" + line="82" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" contentResolver.unregisterContentObserver(settingsObserver)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenTargetFilter.kt" + line="100" + column="25"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java" + line="275" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java" + line="276" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java" + line="277" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(Global.getUriFor(Global.MOBILE_DATA)," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java" + line="200" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(Global.getUriFor(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java" + line="202" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java" + line="212" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" context.contentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt" + line="54" + column="33"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="253" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="256" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="259" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="262" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mAssistContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java" + line="295" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java" + line="395" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java" + line="400" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java" + line="3675" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mSettingsChangeObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java" + line="4705" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(Settings.Global.getUriFor(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java" + line="191" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java" + line="205" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" resolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/power/PowerUI.java" + line="216" + column="18"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java" + line="178" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java" + line="186" + column="39"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java" + line="83" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mContentObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java" + line="100" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java" + line="219" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContentResolver.unregisterContentObserver(mObserver);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java" + line="241" + column="26"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java" + line="243" + column="30"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java" + line="1235" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java" + line="1236" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mContext.getContentResolver().unregisterContentObserver(this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java" + line="1240" + column="43"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.unregisterContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.unregisterContentObserver()`" + errorLine1=" mResolver.unregisterContentObserver(this);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java" + line="377" + column="27"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java" + line="379" + column="23"/> + </issue> + + <issue + id="RegisterContentObserverViaContentResolver" + message="`ContentResolver.registerContentObserver()` should be replaced with an appropriate interface API call, for eg.`<SettingsProxy>/<UserSettingsProxy>.registerContentObserver()`" + errorLine1=" mResolver.registerContentObserver(" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java" + line="381" + column="23"/> + </issue> + </issues> 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 4850085c4b4e..d244482c05ee 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 @@ import java.util.Optional; @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 0e98b840942b..b85e32b381df 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 @@ import java.util.Optional; @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 204d4b09f3ae..38ea44976175 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { // 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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { } @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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { } @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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { } @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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { } @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 @@ class ShadeTouchHandlerTest : SysuiTestCase() { } @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/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt index ad7385344fac..d6712f09cd4e 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 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() { 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 12552489496d..cc945d63e15f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -533,6 +533,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) + advanceTimeBy(600L) + keyguardRepository.setDreaming(true) keyguardRepository.setDreamingWithOverlay(true) advanceTimeBy(60L) @@ -641,6 +643,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) + advanceTimeBy(600L) keyguardRepository.setDreaming(true) keyguardRepository.setDreamingWithOverlay(true) advanceTimeBy(60L) @@ -699,6 +702,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { keyguardRepository.setDozeTransitionModel( DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) ) + advanceTimeBy(600L) keyguardRepository.setDreaming(true) keyguardRepository.setDreamingWithOverlay(true) advanceTimeBy(60L) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 6412276ba34b..3895595aaea6 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.Before; 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 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { // 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 5c09777ddde5..7a86e57779b9 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 @@ class DreamOverlayServiceTest : SysuiTestCase() { // 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/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt new file mode 100644 index 000000000000..1f733472cbed --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt @@ -0,0 +1,82 @@ +/* + * 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.domain.ui.view + +import android.content.applicationContext +import android.widget.Toast +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.education.domain.interactor.KeyboardTouchpadEduInteractor +import com.android.systemui.education.domain.interactor.contextualEducationInteractor +import com.android.systemui.education.domain.interactor.keyboardTouchpadEduInteractor +import com.android.systemui.education.ui.view.ContextualEduUiCoordinator +import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +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.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ContextualEduUiCoordinatorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val interactor = kosmos.contextualEducationInteractor + private lateinit var underTest: ContextualEduUiCoordinator + @Mock private lateinit var toast: Toast + + @get:Rule val mockitoRule = MockitoJUnit.rule() + + @Before + fun setUp() { + val viewModel = + ContextualEduViewModel( + kosmos.applicationContext.resources, + kosmos.keyboardTouchpadEduInteractor + ) + underTest = + ContextualEduUiCoordinator(kosmos.applicationCoroutineScope, viewModel) { _ -> toast } + underTest.start() + kosmos.keyboardTouchpadEduInteractor.start() + } + + @Test + @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) + fun showToastOnNewEdu() = + testScope.runTest { + triggerEducation(BACK) + runCurrent() + verify(toast).show() + } + + private suspend fun triggerEducation(gestureType: GestureType) { + for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { + interactor.incrementSignalCount(gestureType) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 273e3cbe76ea..fd4ed3896c43 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 @@ class QSLongPressEffectTest : SysuiTestCase() { } @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 index 032794c43f08..638c957c9fa7 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.BiometricUnlockMode 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 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() { @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 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() { @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 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() { @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 fc827a1478c7..ebefb4d51943 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.fakeCommandQueue 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 com.google.common.truth.Truth.assertThat 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 @@ class KeyguardInteractorTest : SysuiTestCase() { 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 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + fun isAbleToDream_falseWhenDozing() = + testScope.runTest { + val isAbleToDream by collectLastValue(underTest.isAbleToDream) + + repository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE_AOD) + ) + + assertThat(isAbleToDream).isEqualTo(false) + } + + @Test + fun isAbleToDream_falseWhenNotDozingAndNotDreaming() = + testScope.runTest { + val isAbleToDream by collectLastValue(underTest.isAbleToDream) + + repository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + powerInteractor.setAwakeForTest() + advanceTimeBy(1000L) + + assertThat(isAbleToDream).isEqualTo(false) + } + + @Test + fun isAbleToDream_trueWhenNotDozingAndIsDreaming_afterDelay() = + testScope.runTest { + val isAbleToDream by collectLastValue(underTest.isAbleToDream) + runCurrent() + + repository.setDreaming(true) + repository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + powerInteractor.setAwakeForTest() + runCurrent() + + // After some delay, still false + advanceTimeBy(300L) + assertThat(isAbleToDream).isEqualTo(false) + + // After more delay, is true + advanceTimeBy(300L) + assertThat(isAbleToDream).isEqualTo(true) + + // Also changes back after the minimal debounce + repository.setDreaming(false) + advanceTimeBy(55L) + assertThat(isAbleToDream).isEqualTo(false) + } + + @Test @EnableSceneContainer fun animationDozingTransitions() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 9762fd8e2158..8c1e8de315b1 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // 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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @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 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest 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 3f6e2291fd1f..df8afdbcf7a8 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.FakeFingerprintPropertyRe 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 @@ class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() { } @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/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt index 16c70901eacc..8f925d52e649 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.data.repository.fakeDeviceEntryFingerprintA 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 @@ import org.junit.runner.RunWith @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 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { @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 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { 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 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { 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 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { @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 @@ class NotificationsShadeSceneViewModelTest : SysuiTestCase() { 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/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 3ca802eb7091..036380853a71 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.resolver.homeSceneFamilyResolver 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 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { 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 index a7a3a0f3008a..647fdf6d6931 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.data.repository.fakeDeviceEntryFingerprintA 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.ExperimentalCoroutinesApi 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 @@ import org.junit.runner.RunWith @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 @@ class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { 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 @@ class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { 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 @@ class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { ) 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 @@ class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { 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 000000000000..e322e38ac1e1 --- /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 72a5cd130427..66e45ab8ccbe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -63,8 +63,10 @@ import com.android.systemui.scene.shared.model.Scenes 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.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 @@ -147,7 +149,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ) } - private lateinit var shadeSceneViewModel: ShadeSceneViewModel + private lateinit var shadeSceneContentViewModel: ShadeSceneContentViewModel + private lateinit var shadeSceneActionsViewModel: ShadeSceneActionsViewModel private val powerInteractor by lazy { kosmos.powerInteractor } @@ -186,12 +189,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { 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) @@ -220,8 +226,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() = testScope.runTest { - val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions) - 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, @@ -240,8 +246,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions) - 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, @@ -251,7 +257,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @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) @@ -260,7 +266,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { 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( @@ -271,7 +277,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @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) @@ -288,7 +294,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { 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( @@ -346,8 +352,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = testScope.runTest { unlockDevice() - val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions) - 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) } @@ -368,8 +374,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions) - 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, @@ -386,8 +392,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun bouncerActionButtonClick_opensEmergencyServicesDialer() = testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions) - 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) @@ -406,8 +412,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() - val destinationScenes by collectLastValue(lockscreenSceneActionsViewModel.actions) - 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/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index 8a4319805802..fadb1d7c91a1 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.compose.animation.scene.ObservableTransitionState 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 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@EnableSceneContainer class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -50,7 +51,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { 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 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { 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 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { // 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/OverlayShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt index 0ffabd807ba7..3f087b48f509 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.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.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos @@ -37,6 +38,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi 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 @@ class OverlayShadeViewModelTest : SysuiTestCase() { 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 3ded8a346ce9..f6fe667ff813 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.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.plugins.activityStarter import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -52,6 +53,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + underTest.activateIn(testScope) } @Test @@ -107,15 +109,15 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @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 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { 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 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { 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/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt index 3b2c9819c9b7..06a02c65bc64 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt @@ -27,7 +27,6 @@ import com.android.compose.animation.scene.SwipeDirection import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -37,8 +36,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn -import com.android.systemui.media.controls.data.repository.mediaFilterRepository -import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -49,14 +46,10 @@ 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 @@ -70,7 +63,7 @@ import org.junit.runner.RunWith @TestableLooper.RunWithLooper @EnableSceneContainer @DisableFlags(DualShade.FLAG_NAME) -class ShadeSceneViewModelTest : SysuiTestCase() { +class ShadeSceneActionsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope @@ -78,7 +71,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val shadeRepository by lazy { kosmos.shadeRepository } private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter } - private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel } + private val underTest: ShadeSceneActionsViewModel by lazy { kosmos.shadeSceneActionsViewModel } @Before fun setUp() { @@ -88,13 +81,13 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @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) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) } @@ -102,14 +95,14 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @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) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) setDeviceEntered(true) - assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) } @@ -117,14 +110,14 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun upTransitionSceneKey_keyguardDisabled_gone() = testScope.runTest { - val destinationScenes by collectLastValue(underTest.destinationScenes) + val actions by collectLastValue(underTest.actions) val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false) - assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) } @@ -132,7 +125,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @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( @@ -140,7 +133,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Lockscreen) } @@ -148,7 +141,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @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( @@ -157,7 +150,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") - assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(SceneFamilies.Home) assertThat(homeScene).isEqualTo(Scenes.Gone) } @@ -165,78 +158,22 @@ class ShadeSceneViewModelTest : SysuiTestCase() { @Test fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() = testScope.runTest { - val destinationScenes by collectLastValue(underTest.destinationScenes) + val actions by collectLastValue(underTest.actions) shadeRepository.setShadeLayoutWide(true) runCurrent() - assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey) + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey) .isEqualTo(ToSplitShade) } @Test fun upTransitionKey_splitShadeDisable_isNull() = testScope.runTest { - val destinationScenes by collectLastValue(underTest.destinationScenes) + val actions by collectLastValue(underTest.actions) 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() + assertThat(actions?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull() } @Test @@ -244,8 +181,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { 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() + val actions by collectLastValue(underTest.actions) + assertThat(actions?.get(Swipe(SwipeDirection.Down))?.toScene).isNull() } @Test @@ -253,90 +190,25 @@ class ShadeSceneViewModelTest : SysuiTestCase() { 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) + val actions by collectLastValue(underTest.actions) + assertThat(actions?.get(Swipe(SwipeDirection.Down))?.toScene) .isEqualTo(Scenes.QuickSettings) } @Test fun upTransitionSceneKey_customizing_noTransition() = testScope.runTest { - val destinationScenes by collectLastValue(underTest.destinationScenes) + val actions by collectLastValue(underTest.actions) qsSceneAdapter.setCustomizing(true) assertThat( - destinationScenes!!.keys.filterIsInstance<Swipe>().filter { + actions!!.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. @@ -362,9 +234,4 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ) runCurrent() } - - private data class Translations( - val start: Float, - val end: Float, - ) } 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 000000000000..558606f00300 --- /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/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 2fb9e1e038c8..733cac99f4ec 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.aodBurnInViewModel 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S @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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S @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) @@ -175,7 +179,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S // 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S // 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/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt index ab184abdc963..f232d52615a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.volume.panel.domain.model.ComponentModel import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.ui.composable.enabledComponents +import com.android.systemui.volume.shared.volumePanelLogger import com.google.common.truth.Truth.assertThat import javax.inject.Provider import kotlinx.coroutines.test.runTest @@ -49,6 +50,7 @@ class ComponentsInteractorImplTest : SysuiTestCase() { enabledComponents, { defaultCriteria }, testScope.backgroundScope, + volumePanelLogger, criteriaByKey, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt index 420b955e88e0..51a70bda6034 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt @@ -24,21 +24,30 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dump.DumpManager import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.statusbar.policy.fakeConfigurationController import com.android.systemui.testKosmos +import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory import com.android.systemui.volume.panel.data.repository.volumePanelGlobalStateRepository import com.android.systemui.volume.panel.domain.interactor.criteriaByKey +import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider import com.android.systemui.volume.panel.ui.composable.componentByKey import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager +import com.android.systemui.volume.shared.volumePanelLogger import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -55,6 +64,7 @@ class VolumePanelViewModelTest : SysuiTestCase() { volumePanelGlobalStateRepository.updateVolumePanelState { it.copy(isVisible = true) } } + private val realDumpManager = DumpManager() private val testableResources = context.orCreateTestableResources private lateinit var underTest: VolumePanelViewModel @@ -124,6 +134,60 @@ class VolumePanelViewModelTest : SysuiTestCase() { } @Test + fun testDumpableRegister_unregister() = + with(kosmos) { + testScope.runTest { + val job = launch { + applicationCoroutineScope = this + underTest = createViewModel() + + runCurrent() + + assertThat(realDumpManager.getDumpables().any { it.name == DUMPABLE_NAME }) + .isTrue() + } + + runCurrent() + job.cancel() + + assertThat(realDumpManager.getDumpables().any { it.name == DUMPABLE_NAME }).isTrue() + } + } + + @Test + fun testDumpingState() = + test({ + componentByKey = + mapOf( + COMPONENT_1 to mockVolumePanelUiComponentProvider, + COMPONENT_2 to mockVolumePanelUiComponentProvider, + BOTTOM_BAR to mockVolumePanelUiComponentProvider, + ) + criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria }) + }) { + testScope.runTest { + runCurrent() + + StringWriter().use { + underTest.dump(PrintWriter(it), emptyArray()) + + assertThat(it.buffer.toString()) + .isEqualTo( + "volumePanelState=" + + "VolumePanelState(orientation=1, isLargeScreen=false)\n" + + "componentsLayout=( " + + "headerComponents= " + + "contentComponents=" + + "test_component:1:visible=true, " + + "test_component:2:visible=false " + + "footerComponents= " + + "bottomBarComponent=test_bottom_bar:visible=true )\n" + ) + } + } + } + + @Test fun dismissBroadcast_dismissesPanel() = test { testScope.runTest { runCurrent() // run the flows to let allow the receiver to be registered @@ -140,11 +204,26 @@ class VolumePanelViewModelTest : SysuiTestCase() { private fun test(setup: Kosmos.() -> Unit = {}, test: Kosmos.() -> Unit) = with(kosmos) { setup() - underTest = volumePanelViewModel + underTest = createViewModel() + test() } + private fun Kosmos.createViewModel(): VolumePanelViewModel = + VolumePanelViewModel( + context.orCreateTestableResources.resources, + applicationCoroutineScope, + volumePanelComponentFactory, + configurationController, + broadcastDispatcher, + realDumpManager, + volumePanelLogger, + volumePanelGlobalStateInteractor, + ) + private companion object { + const val DUMPABLE_NAME = "VolumePanelViewModel" + const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar" const val COMPONENT_1: VolumePanelComponentKey = "test_component:1" const val COMPONENT_2: VolumePanelComponentKey = "test_component:2" diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml index ed1c6c723543..badbd8845050 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/raw/action_key_edu.json b/packages/SystemUI/res/raw/action_key_edu.json new file mode 100644 index 000000000000..014d83798da9 --- /dev/null +++ b/packages/SystemUI/res/raw/action_key_edu.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":181,"w":554,"h":564,"nm":"Trackpad-JSON_ActionKey-EDU","ddd":0,"assets":[{"id":"comp_0","nm":"BlankButton","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,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":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"actionKey_themed","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0.288,-0.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.579,-0.158],[0.605,0],[1.21,1.21],[0,1.684],[-1.184,1.184],[-1.684,0],[-1.21,-1.21],[0,-1.71],[0.184,-0.553],[0.316,-0.474],[0,0],[0,0]],"o":[[-0.474,0.316],[-0.553,0.158],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.184],[0,0.605],[-0.158,0.553],[0,0],[0,0],[0,0]],"v":[[10.241,12.155],[8.663,12.866],[6.926,13.103],[2.585,11.287],[0.809,6.946],[2.585,2.605],[6.926,0.789],[11.307,2.605],[13.122,6.946],[12.846,8.682],[12.136,10.222],[16.911,14.997],[15.017,16.891]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.184],[-1.684,0],[-1.184,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0],[1.21,1.184],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,11.327],[-16.911,6.985],[-15.096,2.605],[-10.754,0.789],[-6.374,2.605],[-4.558,6.985],[-6.374,11.327],[-10.754,13.142]],"c":true}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[-8.268,9.432],[-7.242,6.985],[-8.268,4.499],[-10.754,3.473],[-13.201,4.499],[-14.188,6.985],[-13.201,9.432],[-10.754,10.419]],"c":true}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[9.413,9.432],[10.439,6.985],[9.413,4.499],[6.926,3.473],[4.479,4.499],[3.453,6.985],[4.479,9.432],[6.926,10.419]],"c":true}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0],[1.21,1.21],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,-6.354],[-16.911,-10.695],[-15.096,-15.076],[-10.754,-16.891],[-6.374,-15.076],[-4.558,-10.695],[-6.374,-6.354],[-10.754,-4.539]],"c":true}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0]],"v":[[2.585,-6.354],[0.77,-10.695],[2.585,-15.076],[6.926,-16.891],[11.307,-15.076],[13.122,-10.695],[11.307,-6.354],[6.926,-4.539]],"c":true}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[-8.268,-8.248],[-7.242,-10.695],[-8.268,-13.182],[-10.754,-14.208],[-13.201,-13.182],[-14.188,-10.695],[-13.201,-8.248],[-10.754,-7.222]],"c":true}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[9.413,-8.248],[10.439,-10.695],[9.413,-13.182],[6.926,-14.208],[4.479,-13.182],[3.453,-10.695],[4.479,-8.248],[6.926,-7.222]],"c":true}},"nm":"Path 8","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,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":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.33],"y":[0.52]},"t":34,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.36],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":40,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":46,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.33],"y":[0.52]},"t":124,"s":[0]},{"i":{"x":[0.64],"y":[1]},"o":{"x":[0.36],"y":[0]},"t":127,"s":[100]},{"i":{"x":[0.64],"y":[0.48]},"o":{"x":[0.36],"y":[0]},"t":130,"s":[100]},{"t":136,"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,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.92549020052,0.752941191196,0.423529416323,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":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.072,"y":0.635},"o":{"x":0.424,"y":0.112},"t":27,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.313,"y":0.131},"t":39,"s":[40,49.21,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":57,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.07,"y":0.63},"o":{"x":0.42,"y":0.11},"t":117,"s":[40,39.79,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.313,"y":0.131},"t":129,"s":[40,49.21,0],"to":[0,0,0],"ti":[0,0,0]},{"t":147,"s":[40,39.79,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,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":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,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":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"AllApps_Tray_themed","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-8.859,0]},"a":{"a":0,"k":[277,256.562,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":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,330.391]},"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":"Row 4 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,330.391]},"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":"Row 4 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,330.391]},"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":"Row 4 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,330.391]},"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":"Row 4 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,330.391]},"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":"Row 4 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,330.391]},"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":"Row 4 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,281.172]},"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":"Row 3 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,281.172]},"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":"Row 3 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,281.172]},"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":"Row 3 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,281.172]},"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":"Row 3 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,281.172]},"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":"Row 3 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,281.172]},"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":"Row 3 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,231.953]},"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":"Row 2 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,231.953]},"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":"Row 2 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,231.953]},"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":"Row 2 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,231.953]},"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":"Row 2 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,231.953]},"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":"Row 2 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,231.953]},"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":"Row 2 App - 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Safety app","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[400.047,182.734]},"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":"Row 1 App - 6","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Settings","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[350.828,182.734]},"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":"Row 1 App - 5","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"SoundCloud","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[301.609,182.734]},"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":"Row 1 App - 4","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Starbucks","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.391,182.734]},"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":"Row 1 App - 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Snapchat","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[203.172,182.734]},"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":"Row 1 App - 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[29.531,29.531]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.32549020648,0.270588248968,0.164705887437,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":"Spotify","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[153.953,182.734]},"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":"Row 1 App - 1","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-129.938,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":[275.625,25.594]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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":"560x52","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0,-151.594,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":[15.75,1.969]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":118.125},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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":"handle","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.1,"y":1},"o":{"x":0.05,"y":0.7},"t":45,"s":[277,516,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.214,"y":0.214},"t":75,"s":[277,265.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.999,"y":1},"o":{"x":0.3,"y":0},"t":135,"s":[277,265.422,0],"to":[0,0,0],"ti":[0,0,0]},{"t":147,"s":[277,516,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":[316.969,320.906]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":13.78},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,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":"all apps","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"AK_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Scale Down","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[50]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":135,"s":[50]},{"t":144,"s":[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.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.153,0.153,0.153],"y":[0.074,0.074,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":75,"s":[95,95,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":135,"s":[95,95,100]},{"t":165,"s":[100,100,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}]}],"shapes":[],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[70],"t":135,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":144,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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,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 - 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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[133,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[229,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"BlankButton","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[421,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"actionKey_themed","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[325,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"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,"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.32549020648,0.270588248968,0.164705887437,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},{"ddd":0,"ind":6,"ty":0,"nm":"AllApps_Tray_themed","tt":1,"tp":5,"refId":"comp_2","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":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"AK_LofiLauncher","refId":"comp_3","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":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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":"fl","c":{"a":0,"k":[0.325490196078,0.270588235294,0.164705882353,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":"illustrations: action key","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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.850980392157,0.76862745098,0.627450980392,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":180,"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.850980392157,0.76862745098,0.627450980392,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}],"markers":[],"props":{}}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/action_key_success.json b/packages/SystemUI/res/raw/action_key_success.json new file mode 100644 index 000000000000..cae7344d04b9 --- /dev/null +++ b/packages/SystemUI/res/raw/action_key_success.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":50,"w":554,"h":564,"nm":"Trackpad-JSON_ActionKey-Success","ddd":0,"assets":[{"id":"comp_0","nm":"TrackpadAK_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":".secondaryFixedDim","cl":"secondaryFixedDim","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.850980392157,0.76862745098,0.627450980392,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":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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.325490196078,0.270588235294,0.164705882353,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":"actionKey_themed-static","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[0.288,-0.035,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.579,-0.158],[0.605,0],[1.21,1.21],[0,1.684],[-1.184,1.184],[-1.684,0],[-1.21,-1.21],[0,-1.71],[0.184,-0.553],[0.316,-0.474],[0,0],[0,0]],"o":[[-0.474,0.316],[-0.553,0.158],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.184],[0,0.605],[-0.158,0.553],[0,0],[0,0],[0,0]],"v":[[10.241,12.155],[8.663,12.866],[6.926,13.103],[2.585,11.287],[0.809,6.946],[2.585,2.605],[6.926,0.789],[11.307,2.605],[13.122,6.946],[12.846,8.682],[12.136,10.222],[16.911,14.997],[15.017,16.891]],"c":true}},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.184],[-1.684,0],[-1.184,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.736],[1.21,-1.21],[1.736,0],[1.21,1.184],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,11.327],[-16.911,6.985],[-15.096,2.605],[-10.754,0.789],[-6.374,2.605],[-4.558,6.985],[-6.374,11.327],[-10.754,13.142]],"c":true}},"nm":"Path 2","hd":false},{"ind":2,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[-8.268,9.432],[-7.242,6.985],[-8.268,4.499],[-10.754,3.473],[-13.201,4.499],[-14.188,6.985],[-13.201,9.432],[-10.754,10.419]],"c":true}},"nm":"Path 3","hd":false},{"ind":3,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.658],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.658],[0.973,0]],"v":[[9.413,9.432],[10.439,6.985],[9.413,4.499],[6.926,3.473],[4.479,4.499],[3.453,6.985],[4.479,9.432],[6.926,10.419]],"c":true}},"nm":"Path 4","hd":false},{"ind":4,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.184,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.736,0],[1.21,1.21],[0,1.684],[-1.184,1.21],[-1.684,0]],"v":[[-15.096,-6.354],[-16.911,-10.695],[-15.096,-15.076],[-10.754,-16.891],[-6.374,-15.076],[-4.558,-10.695],[-6.374,-6.354],[-10.754,-4.539]],"c":true}},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":0,"k":{"i":[[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0],[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0]],"o":[[-1.21,-1.21],[0,-1.71],[1.21,-1.21],[1.71,0],[1.21,1.21],[0,1.684],[-1.21,1.21],[-1.684,0]],"v":[[2.585,-6.354],[0.77,-10.695],[2.585,-15.076],[6.926,-16.891],[11.307,-15.076],[13.122,-10.695],[11.307,-6.354],[6.926,-4.539]],"c":true}},"nm":"Path 6","hd":false},{"ind":6,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.658,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.658,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[-8.268,-8.248],[-7.242,-10.695],[-8.268,-13.182],[-10.754,-14.208],[-13.201,-13.182],[-14.188,-10.695],[-13.201,-8.248],[-10.754,-7.222]],"c":true}},"nm":"Path 7","hd":false},{"ind":7,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0],[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0]],"o":[[0.684,-0.684],[0,-0.973],[-0.684,-0.684],[-0.947,0],[-0.684,0.684],[0,0.947],[0.684,0.684],[0.973,0]],"v":[[9.413,-8.248],[10.439,-10.695],[9.413,-13.182],[6.926,-14.208],[4.479,-13.182],[3.453,-10.695],[4.479,-8.248],[6.926,-7.222]],"c":true}},"nm":"Path 8","hd":false},{"ty":"fl","c":{"a":0,"k":[0.145098045468,0.101960785687,0.015686275437,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":"icon","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primaryFixedDim","cl":"primaryFixedDim","parent":3,"sr":1,"ks":{"o":{"a":0,"k":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,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.92549020052,0.752941191196,0.423529416323,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":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980392157,0.76862745098,0.627450980392,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":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,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":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"BlankButton","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,39.79,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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":"shortcut symbols","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","sr":1,"ks":{"o":{"a":0,"k":80},"r":{"a":0,"k":0},"p":{"a":0,"k":[40,49.21,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":[80,79.581]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":14.032},"nm":"Rectangle Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.364705890417,0.258823543787,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":"shadow","bm":0,"hd":false}],"ip":0,"op":600,"st":0,"ct":1,"bm":0}]},{"id":"comp_3","nm":"AK_LofiLauncher","fr":60,"pfr":1,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Scale Down","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":45,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[70]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":135,"s":[70]},{"t":144,"s":[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.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.153,0.153,0.153],"y":[0.074,0.074,0]},"t":45,"s":[100,100,100]},{"i":{"x":[0.841,0.841,0.841],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"t":75,"s":[95,95,100]},{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":135,"s":[95,95,100]},{"t":165,"s":[100,100,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}]}],"shapes":[],"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":3,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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":4,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","parent":1,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[80],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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,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 - 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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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.325490196078,0.270588235294,0.164705882353,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}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"TrackpadAK_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":2,"ty":4,"nm":".onSecondaryFixed","cl":"onSecondaryFixed","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":2}},{"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.145098039216,0.101960784314,0.01568627451,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":3,"ty":0,"nm":"actionKey_themed-static","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[325,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[421,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[229,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"BlankButton","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[133,455.5,0]},"a":{"a":0,"k":[40,44.5,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"w":80,"h":89,"ip":0,"op":600,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"AK_LofiLauncher","refId":"comp_3","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":8,"ty":4,"nm":".onSecondaryFixedVariant","cl":"onSecondaryFixedVariant","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":2}},{"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.325490196078,0.270588235294,0.164705882353,1]},"o":{"a":0,"k":50},"r":1,"bm":0,"nm":"Fill 1","hd":false}],"ip":0,"op":50,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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":"fl","c":{"a":0,"k":[0.850980401039,0.768627464771,0.627451002598,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},{"ddd":0,"ind":10,"ty":4,"nm":".secondaryFixedDim","cl":"secondaryFixedDim","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.850980392157,0.76862745098,0.627450980392,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.850980392157,0.76862745098,0.627450980392,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/strings.xml b/packages/SystemUI/res/values/strings.xml index e49f8c84b560..a5fd5b95315e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -971,8 +971,8 @@ <string name="hearing_devices_presets_error">Couldn\'t update preset</string> <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]--> <string name="hearing_devices_preset_label">Preset</string> - <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]--> - <string name="live_caption_title">Live Caption</string> + <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]--> + <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string> <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] --> <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string> @@ -1367,13 +1367,18 @@ <!-- 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> @@ -3674,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] --> @@ -3688,19 +3694,29 @@ <!-- 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 gesture was done successfully [CHAR LIMIT=NONE] --> - <string name="touchpad_tutorial_gesture_done">Great job!</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_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_done">Nice!</string> + <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_finished">You completed the go home gesture.</string> + <string name="touchpad_home_gesture_success_body">You completed the go home gesture.</string> + + <!-- KEYBOARD TUTORIAL--> + <!-- Action key tutorial title [CHAR LIMIT=NONE] --> + <string name="tutorial_action_key_title">Action key</string> + <!-- Action key tutorial guidance[CHAR LIMIT=NONE] --> + <string name="tutorial_action_key_guidance">To access your apps, press the action key on your keyboard.</string> + <!-- Screen title after action key pressed successfully [CHAR LIMIT=NONE] --> + <string name="tutorial_action_key_success_title">Congratulations!</string> + <!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] --> + <string name="tutorial_action_key_success_body">You completed the action key gesture.\n\nAction + / shows all the shortcuts you have available.</string> <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] --> <string name="keyboard_backlight_dialog_title">Keyboard backlight</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index 317201d2c2d9..f358ba2d3ccd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -125,6 +125,7 @@ public class FloatingRotationButton implements RotationButton { taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft); final int diameter = res.getDimensionPixelSize(mButtonDiameterResource); + mKeyButtonView.setDiameter(diameter); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, taskbarMarginBottom)); } @@ -195,6 +196,7 @@ public class FloatingRotationButton implements RotationButton { public void updateIcon(int lightIconColor, int darkIconColor) { mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext() .getDrawable(mRotationButtonController.getIconResId()); + mAnimatedDrawable.setBounds(0, 0, mKeyButtonView.getWidth(), mKeyButtonView.getHeight()); mKeyButtonView.setImageDrawable(mAnimatedDrawable); mKeyButtonView.setColors(lightIconColor, darkIconColor); } @@ -248,8 +250,14 @@ public class FloatingRotationButton implements RotationButton { updateDimensionResources(); if (mIsShowing) { + updateIcon(mRotationButtonController.getLightIconColor(), + mRotationButtonController.getDarkIconColor()); final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams); + if (mAnimatedDrawable != null) { + mAnimatedDrawable.reset(); + mAnimatedDrawable.start(); + } } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java index 2145166e9bc5..75412f94ccb1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java @@ -37,6 +37,7 @@ public class FloatingRotationButtonView extends ImageView { private static final float BACKGROUND_ALPHA = 0.92f; private KeyButtonRipple mRipple; + private int mDiameter; private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Configuration mLastConfiguration; @@ -93,10 +94,25 @@ public class FloatingRotationButtonView extends ImageView { mRipple.setDarkIntensity(darkIntensity); } + /** + * Sets the view's diameter. + * + * @param diameter the diameter value for the view + */ + void setDiameter(int diameter) { + mDiameter = diameter; + } + @Override public void draw(Canvas canvas) { int d = Math.min(getWidth(), getHeight()); canvas.drawOval(0, 0, d, d, mOvalBgPaint); super.draw(canvas); } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(mDiameter, mDiameter); + } } 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 083f1db07886..d08653c3cf1b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -228,7 +228,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mHearingDeviceItemList = getHearingDevicesList(); if (mPresetsController != null) { activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList); - mPresetsController.setActiveHearingDevice(activeHearingDevice); + mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice); } else { activeHearingDevice = null; } @@ -336,7 +336,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice( mHearingDeviceItemList); - mPresetsController.setActiveHearingDevice(activeHearingDevice); + mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice); mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(), R.layout.hearing_devices_preset_spinner_selected, @@ -499,7 +499,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, 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 b46b8fe4f6c8..664f3f834f86 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 android.widget.TextView; 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 @@ public class HearingDevicesListAdapter extends RecyclerView.Adapter<RecyclerView 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 @@ public class HearingDevicesListAdapter extends RecyclerView.Adapter<RecyclerView 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 @@ public class HearingDevicesListAdapter extends RecyclerView.Adapter<RecyclerView 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/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java index f81124eeeb7f..aa95fd038260 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java @@ -113,7 +113,7 @@ public class HearingDevicesPresetsController implements @Override public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) { @@ -137,7 +137,7 @@ public class HearingDevicesPresetsController implements @Override public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) { @@ -177,22 +177,33 @@ public class HearingDevicesPresetsController implements } /** - * Sets the hearing device for this controller to control the preset. + * Sets the hearing device for this controller to control the preset if it supports + * {@link HapClientProfile}. * * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device + * and support {@link HapClientProfile}. */ - public void setActiveHearingDevice(CachedBluetoothDevice activeHearingDevice) { - mActiveHearingDevice = activeHearingDevice; + public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) { + if (mHapClientProfile == null || activeHearingDevice == null) { + mActiveHearingDevice = null; + return; + } + if (activeHearingDevice.getProfiles().stream().anyMatch( + profile -> profile instanceof HapClientProfile)) { + mActiveHearingDevice = activeHearingDevice; + } else { + mActiveHearingDevice = null; + } } /** * Selects the currently active preset for {@code mActiveHearingDevice} individual device or - * the device group accoridng to whether it supports synchronized presets or not. + * the device group according to whether it supports synchronized presets or not. * * @param presetIndex an index of one of the available presets */ public void selectPreset(int presetIndex) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } mSelectedPresetIndex = presetIndex; @@ -217,7 +228,7 @@ public class HearingDevicesPresetsController implements * @return a list of all known preset info */ public List<BluetoothHapPresetInfo> getAllPresetInfo() { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return emptyList(); } return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter( @@ -230,14 +241,14 @@ public class HearingDevicesPresetsController implements * @return active preset index */ public int getActivePresetIndex() { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE; } return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice()); } private void selectPresetSynchronously(int groupId, int presetIndex) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (DEBUG) { @@ -250,7 +261,7 @@ public class HearingDevicesPresetsController implements } private void selectPresetIndependently(int presetIndex) { - if (mActiveHearingDevice == null) { + if (mActiveHearingDevice == null || mHapClientProfile == null) { return; } if (DEBUG) { 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 d5790a44a887..a093f58b88ba 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 @@ constructor( 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 @@ constructor( } init { - if (Flags.hubmodeFullscreenVerticalSwipe()) { + if (Flags.hubmodeFullscreenVerticalSwipeFix()) { scope.launch { communalViewModel.glanceableTouchAvailable.collect { onGlanceableTouchAvailable(it) @@ -218,7 +219,7 @@ constructor( 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 @@ constructor( 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 06b41de12941..9da9a3a98ef5 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 @@ constructor( private var touchAvailable = false init { - if (Flags.hubmodeFullscreenVerticalSwipe()) { + if (Flags.hubmodeFullscreenVerticalSwipeFix()) { scope.launch { communalViewModel.glanceableTouchAvailable.collect { onGlanceableTouchAvailable(it) @@ -107,7 +107,8 @@ constructor( 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 @@ constructor( 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 190bc1587525..d27e72a9c185 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 @@ public interface TouchHandler { * @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 efa55e90081e..1be6f9e7ca4f 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 @@ public class TouchMonitor { mBoundsFlow.cancel(new CancellationException()); } + for (TouchHandler handler : mHandlers) { + handler.onDestroy(); + } + mInitialized = false; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index b45ebd865c55..24ac542c6266 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.BlurUtils 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 @@ constructor( 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 76c7d2383751..bf6d266ac42f 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.touch.TouchInsetManager; 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 @@ public class DreamOverlayContainerViewController extends } }; + private DisposableHandle mFlowHandle; + @Inject public DreamOverlayContainerViewController( DreamOverlayContainerView containerView, @@ -252,6 +255,17 @@ public class DreamOverlayContainerViewController extends } @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 @@ public class DreamOverlayContainerViewController extends emptyRegion.recycle(); if (dreamHandlesBeingObscured()) { - collectFlow( + mFlowHandle = collectFlow( mView, FlowKt.distinctUntilChanged(combineFlows( mKeyguardTransitionInteractor.isFinishedIn( @@ -295,6 +309,10 @@ public class DreamOverlayContainerViewController extends @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 931066d5c582..4b9e5a024393 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.shade.ShadeExpansionChangeEvent; 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 ee7b6f52ac55..5ba780f9c99d 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.communal.domain.interactor.CommunalInteractor; 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 @@ public class CommunalTouchHandler implements TouchHandler { private final ConfigurationInteractor mConfigurationInteractor; private Boolean mIsEnabled = false; + private ArrayList<Job> mFlows = new ArrayList<>(); + private int mLayoutDirection = LayoutDirection.LTR; @VisibleForTesting @@ -70,17 +76,17 @@ public class CommunalTouchHandler implements TouchHandler { 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 @@ public class CommunalTouchHandler implements TouchHandler { } }); } + + @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 096556fed258..7e2c9f89fa67 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -26,6 +26,7 @@ import com.android.systemui.education.domain.interactor.ContextualEducationInter 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.education.ui.view.ContextualEduUiCoordinator import dagger.Binds import dagger.Lazy import dagger.Module @@ -74,7 +75,7 @@ interface ContextualEducationModule { implLazy.get() } else { // No-op implementation when the flag is disabled. - return NoOpContextualEducationInteractor + return NoOpCoreStartable } } @@ -91,6 +92,8 @@ interface ContextualEducationModule { } @Provides + @IntoMap + @ClassKey(KeyboardTouchpadEduInteractor::class) fun provideKeyboardTouchpadEduInteractor( implLazy: Lazy<KeyboardTouchpadEduInteractor> ): CoreStartable { @@ -98,22 +101,32 @@ interface ContextualEducationModule { implLazy.get() } else { // No-op implementation when the flag is disabled. - return NoOpKeyboardTouchpadEduInteractor + return NoOpCoreStartable } } - } - - private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor { - override fun incrementSignalCount(gestureType: GestureType) {} - override fun updateShortcutTriggerTime(gestureType: GestureType) {} + @Provides + @IntoMap + @ClassKey(ContextualEduUiCoordinator::class) + fun provideContextualEduUiCoordinator( + implLazy: Lazy<ContextualEduUiCoordinator> + ): CoreStartable { + return if (Flags.keyboardTouchpadContextualEducation()) { + implLazy.get() + } else { + // No-op implementation when the flag is disabled. + return NoOpCoreStartable + } + } } +} - private object NoOpContextualEducationInteractor : CoreStartable { - override fun start() {} - } +private object NoOpKeyboardTouchpadEduStatsInteractor : KeyboardTouchpadEduStatsInteractor { + override fun incrementSignalCount(gestureType: GestureType) {} - private object NoOpKeyboardTouchpadEduInteractor : CoreStartable { - override fun start() {} - } + override fun updateShortcutTriggerTime(gestureType: GestureType) {} +} + +private object NoOpCoreStartable : CoreStartable { + override fun start() {} } diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt new file mode 100644 index 000000000000..b446ea2bcb38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt @@ -0,0 +1,68 @@ +/* + * 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.ui.view + +import android.content.Context +import android.widget.Toast +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.education.shared.model.EducationUiType +import com.android.systemui.education.ui.viewmodel.ContextualEduContentViewModel +import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * A class to show contextual education on UI based on the edu produced from + * [ContextualEduViewModel] + */ +@SysUISingleton +class ContextualEduUiCoordinator +constructor( + @Application private val applicationScope: CoroutineScope, + private val viewModel: ContextualEduViewModel, + private val createToast: (String) -> Toast +) : CoreStartable { + + @Inject + constructor( + @Application applicationScope: CoroutineScope, + context: Context, + viewModel: ContextualEduViewModel, + ) : this( + applicationScope, + viewModel, + createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) } + ) + + override fun start() { + applicationScope.launch { + viewModel.eduContent.collect { contentModel -> + if (contentModel.type == EducationUiType.Toast) { + showToast(contentModel) + } + } + } + } + + private fun showToast(model: ContextualEduContentViewModel) { + val toast = createToast(model.message) + toast.show() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt new file mode 100644 index 000000000000..3cba4c8fb110 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt @@ -0,0 +1,21 @@ +/* + * 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.ui.viewmodel + +import com.android.systemui.education.shared.model.EducationUiType + +data class ContextualEduContentViewModel(val message: String, val type: EducationUiType) diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt new file mode 100644 index 000000000000..58276e0759f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt @@ -0,0 +1,51 @@ +/* + * 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.ui.viewmodel + +import android.content.res.Resources +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor +import com.android.systemui.education.shared.model.EducationInfo +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map + +@SysUISingleton +class ContextualEduViewModel +@Inject +constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) { + val eduContent: Flow<ContextualEduContentViewModel> = + interactor.educationTriggered.filterNotNull().map { + ContextualEduContentViewModel(getEduContent(it), it.educationUiType) + } + + private fun getEduContent(educationInfo: EducationInfo): String { + // Todo: also check UiType in educationInfo to determine the string + val resourceId = + when (educationInfo.gestureType) { + GestureType.BACK -> R.string.back_edu_toast_content + GestureType.HOME -> R.string.home_edu_toast_content + GestureType.OVERVIEW -> R.string.overview_edu_toast_content + GestureType.ALL_APPS -> R.string.all_apps_edu_toast_content + } + return resources.getString(resourceId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 562ba369f47a..cd0b3f9b6693 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -58,7 +58,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha // Internal notification frontend dependencies 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 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 4652b2a30eb4..e50c05c7f6ed 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 @@ constructor( 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/tutorial/data/model/TutorialSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt index cfe64e269c95..9f46846f0d91 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt @@ -16,11 +16,6 @@ package com.android.systemui.inputdevice.tutorial.data.model -data class TutorialSchedulerInfo( - val keyboard: DeviceSchedulerInfo = DeviceSchedulerInfo(), - val touchpad: DeviceSchedulerInfo = DeviceSchedulerInfo() -) - data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) { val wasEverConnected: Boolean 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 index 31ff01836428..b9b38954784e 100644 --- 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 @@ -25,21 +25,32 @@ 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.dagger.qualifiers.Background 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.CoroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @SysUISingleton class TutorialSchedulerRepository @Inject -constructor(@Application private val applicationContext: Context) { +constructor( + @Application private val applicationContext: Context, + @Background private val backgroundScope: CoroutineScope +) { private val Context.dataStore: DataStore<Preferences> by - preferencesDataStore(name = DATASTORE_NAME) + preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope) - suspend fun loadData(): TutorialSchedulerInfo { + suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched + + suspend fun wasEverConnected(deviceType: DeviceType): Boolean = + loadData()[deviceType]!!.wasEverConnected + + suspend fun connectTime(deviceType: DeviceType): Long = loadData()[deviceType]!!.connectTime!! + + private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> { return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first() } @@ -51,10 +62,10 @@ constructor(@Application private val applicationContext: Context) { 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 getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> { + return mapOf( + DeviceType.KEYBOARD to getDeviceSchedulerInfo(pref, DeviceType.KEYBOARD), + DeviceType.TOUCHPAD to getDeviceSchedulerInfo(pref, DeviceType.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 index 05e104468f67..b3b8f21a4a4b 100644 --- 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 @@ -16,23 +16,25 @@ package com.android.systemui.inputdevice.tutorial.domain.interactor -import android.content.Context -import android.content.Intent +import android.os.SystemProperties +import android.util.Log 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.dagger.qualifiers.Background import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD 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 kotlin.time.Duration.Companion.hours 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.flow.flow +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch /** @@ -43,62 +45,72 @@ import kotlinx.coroutines.launch 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 + @Background private val backgroundScope: CoroutineScope, + keyboardRepository: KeyboardRepository, + touchpadRepository: TouchpadRepository, + private val repo: TutorialSchedulerRepository ) { + private val isAnyDeviceConnected = + mapOf( + KEYBOARD to keyboardRepository.isAnyKeyboardConnected, + TOUCHPAD to touchpadRepository.isAnyTouchpadConnected + ) + 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 - ) - } + backgroundScope.launch { + // Merging two flows to ensure that launch tutorial is launched consecutively in order + // to avoid race condition + merge(touchpadScheduleFlow, keyboardScheduleFlow).collect { + val tutorialType = resolveTutorialType(it) + launchTutorial(tutorialType) } } } - 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!!) + private val touchpadScheduleFlow = flow { + if (!repo.isLaunched(TOUCHPAD)) { + schedule(TOUCHPAD) + emit(TOUCHPAD) } - 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 val keyboardScheduleFlow = flow { + if (!repo.isLaunched(KEYBOARD)) { + schedule(KEYBOARD) + emit(KEYBOARD) + } } - private fun launchTutorial() { - val intent = Intent(TUTORIAL_ACTION) - intent.addCategory(Intent.CATEGORY_DEFAULT) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) + private suspend fun schedule(deviceType: DeviceType) { + if (!repo.wasEverConnected(deviceType)) { + waitForDeviceConnection(deviceType) + repo.updateConnectTime(deviceType, Instant.now().toEpochMilli()) + } + delay(remainingTimeMillis(start = repo.connectTime(deviceType))) + waitForDeviceConnection(deviceType) + } + + private suspend fun waitForDeviceConnection(deviceType: DeviceType) = + isAnyDeviceConnected[deviceType]!!.filter { it }.first() + + private suspend fun launchTutorial(tutorialType: TutorialType) { + if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH) + repo.updateLaunch(KEYBOARD) + if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH) + repo.updateLaunch(TOUCHPAD) + // TODO: launch tutorial + Log.d(TAG, "Launch tutorial for $tutorialType") + } + + private suspend fun resolveTutorialType(deviceType: DeviceType): TutorialType { + // Resolve the type of tutorial depending on which device are connected when the tutorial is + // launched. E.g. when the keyboard is connected for [LAUNCH_DELAY], both keyboard and + // touchpad are connected, we launch the tutorial for both. + if (repo.isLaunched(deviceType)) return TutorialType.NONE + val otherDevice = if (deviceType == KEYBOARD) TOUCHPAD else KEYBOARD + val isOtherDeviceConnected = isAnyDeviceConnected[otherDevice]!!.first() + if (!repo.isLaunched(otherDevice) && isOtherDeviceConnected) return TutorialType.BOTH + return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD } private fun remainingTimeMillis(start: Long): Long { @@ -107,7 +119,20 @@ constructor( } companion object { - const val TUTORIAL_ACTION = "com.android.systemui.action.TOUCHPAD_TUTORIAL" - private val LAUNCH_DELAY = Duration.ofHours(72).toMillis() + const val TAG = "TutorialSchedulerInteractor" + private val DEFAULT_LAUNCH_DELAY = 72.hours.inWholeMilliseconds + private val LAUNCH_DELAY: Long + get() = + SystemProperties.getLong( + "persist.peripheral_tutorial_delay_ms", + DEFAULT_LAUNCH_DELAY + ) + } + + enum class TutorialType { + KEYBOARD, + TOUCHPAD, + BOTH, + NONE } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt index 90867edd8236..da671e330302 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 @@ constructor( private val featureFlags: FeatureFlags, ) : CoreStartable { override fun start() { + stickyKeysIndicatorCoordinator.get().startListening() if (featureFlags.isEnabled(LegacyFlag.KEYBOARD_BACKLIGHT_INDICATOR)) { keyboardBacklightDialogCoordinator.get().startListening() } - if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) { - stickyKeysIndicatorCoordinator.get().startListening() - } if (Flags.keyboardDockingIndicator()) { keyboardDockingIndicationViewBinder.get().startListening() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 9f3311373709..871d04693452 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 class KeyguardService extends Service { 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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 0b3d0f7160f0..1f3df95b6e28 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2334,6 +2334,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Log.e(TAG, "doKeyguard: we're still showing, but going away. Re-show the " + "keyguard rather than short-circuiting and resetting."); } else { + // We're removing "reset" in the refactor - "resetting" the views will happen + // as a reaction to the root cause of the "reset" signal. + if (KeyguardWmStateRefactor.isEnabled()) { + return; + } + // It's already showing, and we're not trying to show it while the screen is // off. We can simply reset all of the views, but don't hide the bouncer in case // the user is currently interacting with it. @@ -2399,6 +2405,16 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, */ 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 edf17c1e9e80..81b0064f0f03 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 @@ interface KeyguardRepository { /** 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 @@ interface KeyguardRepository { fun dozeTimeTick() + fun showDismissibleKeyguard() + fun setDismissAction(dismissAction: DismissAction) suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) @@ -439,6 +444,12 @@ constructor( _dozeTimeTick.value = systemClock.uptimeMillis() } + override val showDismissibleKeyguard = MutableStateFlow<Long>(0L) + + override fun showDismissibleKeyguard() { + showDismissibleKeyguard.value = systemClock.uptimeMillis() + } + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 3775d191949e..17c1e823a1ca 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( } scope.launch { - keyguardInteractor.isDreaming + keyguardInteractor.isAbleToDream .filter { !it } .sample(deviceEntryInteractor.isUnlocked, ::Pair) .collect { (_, dismissable) -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 8f4110c7cc57..db5a63bbf446 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 @@ constructor( listenForGoneToAodOrDozing() listenForGoneToDreaming() listenForGoneToLockscreenOrHub() + listenForGoneToOccluded() listenForGoneToDreamingLockscreenHosted() } @@ -81,6 +82,27 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 51d92f054bbe..5dc020f41ad3 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 @@ constructor( 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 710b710aa7d5..aea57ce15794 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 @@ constructor( } } + /** Starts a transition to dismiss the keyguard from the OCCLUDED state. */ + fun dismissFromOccluded() { + scope.launch { + startTransitionTo(KeyguardState.GONE, ownerReason = "Dismiss from occluded") + } + } + private fun listenForOccludedToGone() { if (KeyguardWmStateRefactor.isEnabled) { // We don't think OCCLUDED to GONE is possible. You should always have to go via a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 42490c4176c6..0df989e9353f 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 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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,14 +154,23 @@ constructor( val isPulsing: Flow<Boolean> = dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING } + /** 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. + * but not vice-versa. Also accounts for [isDreamingWithOverlay] */ - val isDreaming: StateFlow<Boolean> = repository.isDreaming - - /** Whether the system is dreaming with an overlay active */ - val isDreamingWithOverlay: Flow<Boolean> = repository.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 @@ constructor( 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 @@ constructor( * 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 @@ constructor( 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 e132eb7ec32a..b89eb2723fab 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 @@ constructor( } 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 afbe3579315d..efdae6202805 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -75,6 +75,7 @@ constructor( private val fromAlternateBouncerTransitionInteractor: dagger.Lazy<FromAlternateBouncerTransitionInteractor>, private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>, + private val fromOccludedTransitionInteractor: dagger.Lazy<FromOccludedTransitionInteractor>, private val sceneInteractor: SceneInteractor, ) { private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>() @@ -418,6 +419,7 @@ constructor( 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/SwipeToDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt index 86e41154205e..906d58664de9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SwipeToDismissInteractor.kt @@ -19,14 +19,15 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.sample -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject /** * Handles logic around the swipe to dismiss gesture, where the user swipes up on the dismissable @@ -53,12 +54,14 @@ constructor( shadeRepository.currentFling .sample( transitionInteractor.startedKeyguardState, - keyguardInteractor.isKeyguardDismissible + keyguardInteractor.isKeyguardDismissible, + keyguardInteractor.statusBarState, ) - .filter { (flingInfo, startedState, keyguardDismissable) -> + .filter { (flingInfo, startedState, keyguardDismissable, statusBarState) -> flingInfo != null && - !flingInfo.expand && - startedState == KeyguardState.LOCKSCREEN && + !flingInfo.expand && + statusBarState != StatusBarState.SHADE_LOCKED && + startedState == KeyguardState.LOCKSCREEN && keyguardDismissable } .map { (flingInfo, _) -> flingInfo } 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 e1b333dd1d06..25b2b7cad7ec 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 @@ constructor( 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 @@ constructor( ) { // Dreams dismiss keyguard and return to GONE if they can. false + } else if ( + startedWithPrev.newValue.from == KeyguardState.OCCLUDED && + startedWithPrev.newValue.to == KeyguardState.GONE + ) { + // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs + // when an app uses intent flags to launch over an insecure keyguard without + // dismissing it, and then manually requests keyguard dismissal while + // OCCLUDED. This transition is not user-visible; the device unlocks in the + // background and the app remains on top, while we're now GONE. In this case + // we should simply tell WM that the lockscreen is no longer visible, and + // *not* play the going away animation or related animations. + false } else { // Otherwise, use the visibility of the current state. KeyguardState.lockscreenVisibleInState(currentState) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt index 9dc77d3dc9d3..fb9719142b54 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt @@ -26,6 +26,7 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.scene.shared.flag.SceneContainerFlag import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi @@ -52,7 +53,11 @@ object AlternateBouncerUdfpsViewBinder { } } - launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } } + if (SceneContainerFlag.isEnabled) { + view.alpha = 1f + } else { + launch("$TAG#viewModel.alpha") { viewModel.alpha.collect { view.alpha = it } } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index a250b22dde07..91a7f7fc66bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -37,13 +37,13 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel -import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scrim.ScrimView import dagger.Lazy import javax.inject.Inject @@ -67,7 +67,6 @@ constructor( private val alternateBouncerDependencies: Lazy<AlternateBouncerDependencies>, private val windowManager: Lazy<WindowManager>, private val layoutInflater: Lazy<LayoutInflater>, - private val dismissCallbackRegistry: DismissCallbackRegistry, ) : CoreStartable { private val layoutParams: WindowManager.LayoutParams get() = @@ -95,9 +94,10 @@ constructor( private var alternateBouncerView: ConstraintLayout? = null override fun start() { - if (!DeviceEntryUdfpsRefactor.isEnabled) { + if (!DeviceEntryUdfpsRefactor.isEnabled || SceneContainerFlag.isEnabled) { return } + applicationScope.launch("$TAG#alternateBouncerWindowViewModel") { alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect { addAlternateBouncerWindowView -> @@ -110,7 +110,7 @@ constructor( bind(alternateBouncerView!!, alternateBouncerDependencies.get()) } else { removeViewFromWindowManager() - alternateBouncerDependencies.get().viewModel.hideAlternateBouncer() + alternateBouncerDependencies.get().viewModel.onRemovedFromWindow() } } } @@ -144,7 +144,7 @@ constructor( private val onAttachAddBackGestureHandler = object : View.OnAttachStateChangeListener { private val onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback { - onBackRequested() + alternateBouncerDependencies.get().viewModel.onBackRequested() } override fun onViewAttachedToWindow(view: View) { @@ -161,14 +161,12 @@ constructor( .findOnBackInvokedDispatcher() ?.unregisterOnBackInvokedCallback(onBackInvokedCallback) } - - fun onBackRequested() { - alternateBouncerDependencies.get().viewModel.hideAlternateBouncer() - dismissCallbackRegistry.notifyDismissCancelled() - } } private fun addViewToWindowManager() { + if (SceneContainerFlag.isEnabled) { + return + } if (alternateBouncerView != null) { return } @@ -190,6 +188,7 @@ constructor( if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) { return } + optionallyAddUdfpsViews( view = view, udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel, @@ -202,12 +201,13 @@ constructor( viewModel = alternateBouncerDependencies.messageAreaViewModel, ) - val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView + val scrim: ScrimView = view.requireViewById(R.id.alternate_bouncer_scrim) val viewModel = alternateBouncerDependencies.viewModel val swipeUpAnywhereGestureHandler = alternateBouncerDependencies.swipeUpAnywhereGestureHandler val tapGestureDetector = alternateBouncerDependencies.tapGestureDetector - view.repeatWhenAttached { alternateBouncerViewContainer -> + + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { launch("$TAG#viewModel.registerForDismissGestures") { viewModel.registerForDismissGestures.collect { registerForDismissGestures -> @@ -216,11 +216,11 @@ constructor( swipeTag ) { _ -> alternateBouncerDependencies.powerInteractor.onUserTouch() - viewModel.showPrimaryBouncer() + viewModel.onTapped() } tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ -> alternateBouncerDependencies.powerInteractor.onUserTouch() - viewModel.showPrimaryBouncer() + viewModel.onTapped() } } else { swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback( 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 162a0d233efd..15e6b1d78ea0 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.isVisible 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.flow.map 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 @@ object KeyguardQuickAffordanceViewBinder { 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 @@ object KeyguardQuickAffordanceViewBinder { ) } - launch { + launch("$TAG#configurationBasedDimensions") { configurationBasedDimensions.collect { dimensions -> button.updateLayoutParams<ViewGroup.LayoutParams> { width = dimensions.buttonSizePx.width @@ -131,10 +139,7 @@ object KeyguardQuickAffordanceViewBinder { 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/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 6faca1e28b39..6031ef60e1be 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 androidx.core.view.isInvisible 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.KeyguardRootViewModel 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.clocks.shared.model.ClockPreviewConstants 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 2dc930121a71..bf6f2c44521a 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 @@ abstract class KeyguardBlueprintModule { ): 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 b984a6808d1c..000000000000 --- 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 1ba830bdb1ea..000000000000 --- 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/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 64c46dbf05aa..e558033728ba 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.LEFT 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.binder.KeyguardQuickAffordanceViewBinder 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 @@ constructor( 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 @@ constructor( 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 c590f07d9b50..992550cdca5a 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 @@ constructor( 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/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt index df0b3dc3cbce..4908dbdec61e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shared.recents.utilities.Utilities.clamp import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,6 +51,7 @@ constructor( private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported val alpha: Flow<Float> = alternateBouncerViewModel.transitionToAlternateBouncerProgress.map { + SceneContainerFlag.assertInLegacyMode() clamp(it * 2f, 0f, 1f) } 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 5a559fc3aa45..7b0b23ffb2ff 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 @@ -18,15 +18,20 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Color +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach @ExperimentalCoroutinesApi class AlternateBouncerViewModel @@ -34,17 +39,20 @@ class AlternateBouncerViewModel constructor( private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val dismissCallbackRegistry: DismissCallbackRegistry, + alternateBouncerInteractor: Lazy<AlternateBouncerInteractor>, ) { // When we're fully transitioned to the AlternateBouncer, the alpha of the scrim should be: private val alternateBouncerScrimAlpha = .66f + /** Reports the alternate bouncer visible state if the scene container flag is enabled. */ + val isVisible: Flow<Boolean> = + alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.assertInNewMode() } + /** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */ - val transitionToAlternateBouncerProgress = + val transitionToAlternateBouncerProgress: Flow<Float> = 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 } @@ -54,11 +62,16 @@ constructor( val registerForDismissGestures: Flow<Boolean> = transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged() - fun showPrimaryBouncer() { + fun onTapped() { statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true) } - fun hideAlternateBouncer() { + fun onRemovedFromWindow() { + statusBarKeyguardViewManager.hideAlternateBouncer(false) + } + + fun onBackRequested() { statusBarKeyguardViewManager.hideAlternateBouncer(false) + dismissCallbackRegistry.notifyDismissCancelled() } } 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 680f966708be..2426f9745885 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 @@ package com.android.systemui.keyguard.ui.viewmodel 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.keyguard.shared.quickaffordance.KeyguardQuickAfforda 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 @@ constructor( /** 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 @@ constructor( 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 38a2b1bad3bf..050ef6f94f0a 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 @@ constructor( 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 @@ constructor( merge( alphaOnShadeExpansion, keyguardInteractor.dismissAlpha, + alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState), alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState), alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState), aodToGoneTransitionViewModel.lockscreenAlpha(viewState), diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index 46aa0644035c..2ce7044897be 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -27,7 +27,6 @@ import android.view.ViewGroup import android.window.RemoteTransition import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.android.systemui.Flags.pssAppSelectorAbruptExitFix import com.android.systemui.Flags.pssAppSelectorRecentsSplitScreen import com.android.systemui.display.naturalBounds import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler @@ -160,7 +159,7 @@ constructor( private fun createAnimation(task: RecentTask, view: View): ActivityOptions = - if (pssAppSelectorAbruptExitFix() && task.isForegroundTask) { + if (task.isForegroundTask) { // When the selected task is in the foreground, the scale up animation doesn't work. // We fallback to the default close animation. ActivityOptions.makeCustomTaskAnimation( diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt index 8bf220277c12..4b132d0db3fc 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegate.kt @@ -48,6 +48,7 @@ class ShareToAppPermissionDialogDelegate( appName, hostUid, mediaProjectionMetricsLogger, + dialogIconDrawable = R.drawable.ic_present_to_all, ) { override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) { super.onCreate(dialog, savedInstanceState) @@ -83,22 +84,28 @@ class ShareToAppPermissionDialogDelegate( listOf( ScreenShareOption( mode = SINGLE_APP, - spinnerText = R.string.screen_share_permission_dialog_option_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_app_permission_dialog_continue, + R.string + .media_projection_entry_generic_permission_dialog_continue_single_app, spinnerDisabledText = singleAppDisabledText, ), ScreenShareOption( mode = ENTIRE_SCREEN, - spinnerText = R.string.screen_share_permission_dialog_option_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, + R.string + .media_projection_entry_app_permission_dialog_continue_entire_screen, ) ) return if (singleAppDisabledText != null) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java index e2ba76141845..a8b979e05276 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -16,11 +16,16 @@ package com.android.systemui.navigationbar; +import static com.android.systemui.Flags.enableViewCaptureTracing; +import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy; + import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import com.android.app.viewcapture.ViewCapture; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; @@ -28,6 +33,7 @@ import com.android.systemui.navigationbar.views.NavigationBarFrame; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.res.R; +import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -73,4 +79,15 @@ public interface NavigationBarModule { static WindowManager provideWindowManager(@DisplayId Context context) { return context.getSystemService(WindowManager.class); } + + /** A ViewCaptureAwareWindowManager specific to the display's context. */ + @Provides + @NavigationBarScope + @DisplayId + static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager( + @DisplayId WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) { + return new ViewCaptureAwareWindowManager(windowManager, + /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture), + /* isViewCaptureEnabled= */ enableViewCaptureTracing()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java index 7b248eb876a8..e895d83758f7 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -102,6 +102,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; @@ -196,6 +197,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final Context mContext; private final Bundle mSavedState; private final WindowManager mWindowManager; + private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; private final AccessibilityManager mAccessibilityManager; private final DeviceProvisionedController mDeviceProvisionedController; private final StatusBarStateController mStatusBarStateController; @@ -556,6 +558,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements @Nullable Bundle savedState, @DisplayId Context context, @DisplayId WindowManager windowManager, + @DisplayId ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, DeviceProvisionedController deviceProvisionedController, @@ -601,6 +604,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mContext = context; mSavedState = savedState; mWindowManager = windowManager; + mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager; mAccessibilityManager = accessibilityManager; mDeviceProvisionedController = deviceProvisionedController; mStatusBarStateController = statusBarStateController; @@ -721,7 +725,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView); - mWindowManager.addView(mFrame, + mViewCaptureAwareWindowManager.addView(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration .getRotation())); mDisplayId = mContext.getDisplayId(); @@ -764,7 +768,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mCommandQueue.removeCallback(this); Trace.beginSection("NavigationBar#removeViewImmediate"); try { - mWindowManager.removeViewImmediate(mView.getRootView()); + mViewCaptureAwareWindowManager.removeViewImmediate(mView.getRootView()); } finally { Trace.endSection(); } @@ -866,7 +870,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (mOrientationHandle != null) { resetSecondaryHandle(); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); - mWindowManager.removeView(mOrientationHandle); + mViewCaptureAwareWindowManager.removeView(mOrientationHandle); mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); } @@ -937,7 +941,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId()); mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; - mWindowManager.addView(mOrientationHandle, mOrientationParams); + mViewCaptureAwareWindowManager.addView(mOrientationHandle, mOrientationParams); mOrientationHandle.setVisibility(View.GONE); logNavbarOrientation("initSecondaryHomeHandleForRotation"); 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 index fa8e13ab2b72..2d2b869a49ea 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.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.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 @@ constructor( 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 000000000000..c1c7320c42db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneContentViewModel.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.notifications.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.shade.ui.viewmodel.BaseShadeSceneViewModel +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** + * Models UI state used to render the content of the notifications shade scene. + * + * Different from [NotificationsShadeSceneActionsViewModel], which only models user actions that can + * be performed to navigate to other scenes. + */ +class NotificationsShadeSceneContentViewModel +@AssistedInject +constructor( + deviceEntryInteractor: DeviceEntryInteractor, + sceneInteractor: SceneInteractor, +) : + BaseShadeSceneViewModel( + deviceEntryInteractor, + sceneInteractor, + ) { + + @AssistedFactory + interface Factory { + fun create(): NotificationsShadeSceneContentViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java index 1b34c33c9ca0..89be17beaba1 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 @@ package com.android.systemui.qs; 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 @@ public abstract class UserSettingObserver extends ContentObserver implements Lis 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/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index b1cc55d03b04..7258882e9ffa 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.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 java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -48,10 +47,9 @@ import kotlinx.coroutines.flow.map 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 000000000000..d2967b87b967 --- /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 000000000000..abfca4b9aa4a --- /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 e012f2cac1fb..000000000000 --- 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/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 98a61df4ca55..863a899b6f4c 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.content.res.Resources 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 @@ constructor( 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 @@ constructor( 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 000000000000..2ffcad4d1fc9 --- /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/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 5b5013352c29..3ec088c66e10 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -25,6 +25,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent @@ -132,6 +133,7 @@ constructor( private val keyguardEnabledInteractor: KeyguardEnabledInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, private val statusBarStateController: SysuiStatusBarStateController, + private val alternateBouncerInteractor: AlternateBouncerInteractor, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() @@ -152,6 +154,7 @@ constructor( handleKeyguardEnabledness() notifyKeyguardDismissCallbacks() refreshLockscreenEnabled() + handleHideAlternateBouncerOnTransitionToGone() } else { sceneLogger.logFrameworkEnabled( isEnabled = false, @@ -228,13 +231,16 @@ constructor( }, headsUpInteractor.isHeadsUpOrAnimatingAway, occlusionInteractor.invisibleDueToOcclusion, + alternateBouncerInteractor.isVisible, ) { visibilityForTransitionState, isHeadsUpOrAnimatingAway, invisibleDueToOcclusion, + isAlternateBouncerVisible, -> when { isHeadsUpOrAnimatingAway -> true to "showing a HUN" + isAlternateBouncerVisible -> true to "showing alternate bouncer" invisibleDueToOcclusion -> false to "invisible due to occlusion" else -> visibilityForTransitionState } @@ -351,7 +357,9 @@ constructor( ) } val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen) - val isOnBouncer = renderedScenes.contains(Scenes.Bouncer) + val isOnBouncer = + renderedScenes.contains(Scenes.Bouncer) || + alternateBouncerInteractor.isVisibleState() if (!deviceUnlockStatus.isUnlocked) { return@mapNotNull if (isOnLockscreen || isOnBouncer) { // Already on lockscreen or bouncer, no need to change scenes. @@ -379,12 +387,13 @@ constructor( !statusBarStateController.leaveOpenOnKeyguardHide() ) { Scenes.Gone to - "device was unlocked in Bouncer scene and shade" + + "device was unlocked with bouncer showing and shade" + " didn't need to be left open" } else { val prevScene = previousScene.value (prevScene ?: Scenes.Gone) to - "device was unlocked in Bouncer scene, from sceneKey=$prevScene" + "device was unlocked with bouncer showing," + + " from sceneKey=$prevScene" } isOnLockscreen -> // The lockscreen should be dismissed automatically in 2 scenarios: @@ -776,4 +785,14 @@ constructor( .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() } } } + + private fun handleHideAlternateBouncerOnTransitionToGone() { + applicationScope.launch { + sceneInteractor.transitionState + .map { it.isIdle(Scenes.Gone) } + .distinctUntilChanged() + .filter { it } + .collectLatest { alternateBouncerInteractor.hide() } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index bccbb1130bcc..f6924f222e11 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -5,15 +5,18 @@ import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.WindowInsets +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.shade.TouchLogger import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow /** A root view of the main SysUI window that supports scenes. */ +@ExperimentalCoroutinesApi class SceneWindowRootView( context: Context, attrs: AttributeSet?, @@ -35,6 +38,7 @@ class SceneWindowRootView( scenes: Set<Scene>, layoutInsetController: LayoutInsetsController, sceneDataSourceDelegator: SceneDataSourceDelegator, + alternateBouncerDependencies: AlternateBouncerDependencies, ) { this.viewModel = viewModel setLayoutInsetsController(layoutInsetController) @@ -49,6 +53,7 @@ class SceneWindowRootView( super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE) }, dataSourceDelegator = sceneDataSourceDelegator, + alternateBouncerDependencies = alternateBouncerDependencies, ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index d31d6f4137c1..73a8e4c24578 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -37,6 +37,8 @@ import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider +import com.android.systemui.keyguard.ui.composable.AlternateBouncer +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -48,12 +50,14 @@ import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +@ExperimentalCoroutinesApi object SceneWindowRootViewBinder { /** Binds between the view and view-model pertaining to a specific scene container. */ @@ -66,6 +70,7 @@ object SceneWindowRootViewBinder { scenes: Set<Scene>, onVisibilityChangedInternal: (isVisible: Boolean) -> Unit, dataSourceDelegator: SceneDataSourceDelegator, + alternateBouncerDependencies: AlternateBouncerDependencies, ) { val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key } val sortedSceneByKey: Map<SceneKey, Scene> = buildMap { @@ -120,6 +125,14 @@ object SceneWindowRootViewBinder { sharedNotificationContainer ) view.addView(sharedNotificationContainer) + + // TODO (b/358354906): use an overlay for the alternate bouncer + view.addView( + createAlternateBouncerView( + context = view.context, + alternateBouncerDependencies = alternateBouncerDependencies, + ) + ) } launch { @@ -164,6 +177,19 @@ object SceneWindowRootViewBinder { } } + private fun createAlternateBouncerView( + context: Context, + alternateBouncerDependencies: AlternateBouncerDependencies, + ): ComposeView { + return ComposeView(context).apply { + setContent { + AlternateBouncer( + alternateBouncerDependencies = alternateBouncerDependencies, + ) + } + } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 117035422c51..700253babb82 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 @@ public class RecordingService extends Service implements ScreenMediaRecorderList 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 @@ public class RecordingService extends Service implements ScreenMediaRecorderList "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 @@ public class RecordingService extends Service implements ScreenMediaRecorderList mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START); } else { updateState(false); - createErrorStartingNotification(); + createErrorStartingNotification(currentUser); stopForeground(STOP_FOREGROUND_DETACH); stopSelf(); return Service.START_NOT_STICKY; @@ -276,8 +279,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList * errors. */ @VisibleForTesting - protected void createErrorStartingNotification() { - createErrorNotification(strings().getStartError()); + protected void createErrorStartingNotification(UserHandle currentUser) { + createErrorNotification(currentUser, strings().getStartError(), GROUP_KEY_ERROR_STARTING); } /** @@ -285,17 +288,22 @@ public class RecordingService extends Service implements ScreenMediaRecorderList * errors. */ @VisibleForTesting - protected void createErrorSavingNotification() { - createErrorNotification(strings().getSaveError()); + protected void createErrorSavingNotification(UserHandle currentUser) { + createErrorNotification(currentUser, strings().getSaveError(), GROUP_KEY_ERROR_SAVING); } - private void createErrorNotification(String notificationContentTitle) { + 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()); Notification.Builder builder = new Notification.Builder(this, getChannelId()) .setSmallIcon(R.drawable.ic_screenrecord) .setContentTitle(notificationContentTitle) + .setGroup(groupKey) .addExtras(extras); startForeground(mNotificationId, builder.build()); } @@ -350,7 +358,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList .setContentText( strings().getBackgroundProcessingLabel()) .setSmallIcon(R.drawable.ic_screenrecord) - .setGroup(GROUP_KEY) + .setGroup(GROUP_KEY_SAVED) .addExtras(extras); return builder.build(); } @@ -387,7 +395,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList PendingIntent.FLAG_IMMUTABLE)) .addAction(shareAction) .setAutoCancel(true) - .setGroup(GROUP_KEY) + .setGroup(GROUP_KEY_SAVED) .addExtras(extras); // Add thumbnail if available @@ -402,21 +410,28 @@ public class RecordingService extends Service implements ScreenMediaRecorderList } /** - * 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() { @@ -427,6 +442,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList 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 { @@ -444,7 +460,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList Log.e(getTag(), "stopRecording called, but there was an error when ending" + "recording"); exception.printStackTrace(); - createErrorSavingNotification(); + createErrorSavingNotification(currentUser); } catch (Throwable throwable) { if (getRecorder() != null) { // Something unexpected happen, SystemUI will crash but let's delete @@ -468,7 +484,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList 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) { @@ -527,7 +543,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList private Intent getShareIntent(Context context, Uri path) { return new Intent(context, this.getClass()).setAction(ACTION_SHARE) - .putExtra(EXTRA_PATH, path); + .putExtra(EXTRA_PATH, path) + .putExtra(EXTRA_NOTIFICATION_ID, mNotificationId); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 540d4c43c58d..7b802a2a40aa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -17,7 +17,6 @@ package com.android.systemui.screenshot; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static com.android.systemui.Flags.screenshotSaveImageExporter; import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; @@ -31,7 +30,6 @@ import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERAC import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -52,17 +50,12 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.ScrollCaptureResponse; -import android.view.View; -import android.view.ViewGroup; import android.view.ViewRootImpl; -import android.view.ViewTreeObserver; -import android.view.WindowInsets; import android.view.WindowManager; import android.widget.Toast; import android.window.WindowContext; import com.android.internal.logging.UiEventLogger; -import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; @@ -115,11 +108,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler { private final BroadcastDispatcher mBroadcastDispatcher; private final ScreenshotActionsController mActionsController; - private final WindowManager mWindowManager; - private final WindowManager.LayoutParams mWindowLayoutParams; @Nullable private final ScreenshotSoundController mScreenshotSoundController; - private final PhoneWindow mWindow; + private final ScreenshotWindow mWindow; private final Display mDisplay; private final ScrollCaptureExecutor mScrollCaptureExecutor; private final ScreenshotNotificationSmartActionsProvider @@ -135,8 +126,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler { private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; private boolean mScreenshotTakenInPortrait; - private boolean mAttachRequested; - private boolean mDetachRequested; private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; private String mPackageName = ""; @@ -155,7 +144,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler { @AssistedInject ScreenshotController( Context context, - WindowManager windowManager, + ScreenshotWindow.Factory screenshotWindowFactory, FeatureFlags flags, ScreenshotShelfViewProxy.Factory viewProxyFactory, ScreenshotSmartActions screenshotSmartActions, @@ -195,9 +184,8 @@ public class ScreenshotController implements InteractiveScreenshotHandler { mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); mDisplay = display; - mWindowManager = windowManager; - final Context displayContext = context.createDisplayContext(display); - mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); + mWindow = screenshotWindowFactory.create(mDisplay); + mContext = mWindow.getContext(); mFlags = flags; mUserManager = userManager; mMessageContainerController = messageContainerController; @@ -213,17 +201,10 @@ public class ScreenshotController implements InteractiveScreenshotHandler { mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT); }); - // Setup the window that we are going to use - mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams(); - mWindowLayoutParams.setTitle("ScreenshotAnimation"); - - mWindow = FloatingWindowUtil.getFloatingWindow(mContext); - mWindow.setWindowManager(mWindowManager, null, null); - mConfigChanges.applyNewConfig(context.getResources()); reloadAssets(); - mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy, + mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy, () -> { finishDismiss(); return Unit.INSTANCE; @@ -318,12 +299,12 @@ public class ScreenshotController implements InteractiveScreenshotHandler { } // The window is focusable by default - setWindowFocusable(true); + mWindow.setFocusable(true); mViewProxy.requestFocus(); enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); - attachWindow(); + mWindow.attachWindow(); boolean showFlash; if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { @@ -347,13 +328,10 @@ public class ScreenshotController implements InteractiveScreenshotHandler { mViewProxy.setScreenshot(screenshot); - // ignore system bar insets for the purpose of window layout - mWindow.getDecorView().setOnApplyWindowInsetsListener( - (v, insets) -> WindowInsets.CONSUMED); } void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) { - withWindowAttached(() -> { + mWindow.whenWindowAttached(() -> { mAnnouncementResolver.getScreenshotAnnouncement( screenshot.getUserHandle().getIdentifier(), announcement -> { @@ -444,7 +422,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler { @Override public void onTouchOutside() { // TODO(159460485): Remove this when focus is handled properly in the system - setWindowFocusable(false); + mWindow.setFocusable(false); } }); @@ -457,9 +435,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler { private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). - withWindowAttached(() -> { + mWindow.whenWindowAttached(() -> { requestScrollCapture(requestId, owner); - mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback( + mWindow.setActivityConfigCallback( new ViewRootImpl.ActivityConfigCallback() { @Override public void onConfigurationChanged(Configuration overrideConfig, @@ -472,8 +450,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler { // to set up in the new orientation. mScreenshotHandler.postDelayed( () -> requestScrollCapture(requestId, owner), 150); - mViewProxy.updateInsets( - mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + mViewProxy.updateInsets(mWindow.getWindowInsets()); // Screenshot animation calculations won't be valid anymore, // so just end if (mScreenshotAnimation != null @@ -489,7 +466,7 @@ public class ScreenshotController implements InteractiveScreenshotHandler { private void requestScrollCapture(UUID requestId, UserHandle owner) { mScrollCaptureExecutor.requestScrollCapture( mDisplay.getDisplayId(), - mWindow.getDecorView().getWindowToken(), + mWindow.getWindowToken(), (response) -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, response.getPackageName()); @@ -528,61 +505,9 @@ public class ScreenshotController implements InteractiveScreenshotHandler { mViewProxy::startLongScreenshotTransition); } - private void withWindowAttached(Runnable action) { - View decorView = mWindow.getDecorView(); - if (decorView.isAttachedToWindow()) { - action.run(); - } else { - decorView.getViewTreeObserver().addOnWindowAttachListener( - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - mAttachRequested = false; - decorView.getViewTreeObserver().removeOnWindowAttachListener(this); - action.run(); - } - - @Override - public void onWindowDetached() { - } - }); - - } - } - - @MainThread - private void attachWindow() { - View decorView = mWindow.getDecorView(); - if (decorView.isAttachedToWindow() || mAttachRequested) { - return; - } - if (DEBUG_WINDOW) { - Log.d(TAG, "attachWindow"); - } - mAttachRequested = true; - mWindowManager.addView(decorView, mWindowLayoutParams); - decorView.requestApplyInsets(); - - ViewGroup layout = decorView.requireViewById(android.R.id.content); - layout.setClipChildren(false); - layout.setClipToPadding(false); - } - @Override public void removeWindow() { - final View decorView = mWindow.peekDecorView(); - if (decorView != null && decorView.isAttachedToWindow()) { - if (DEBUG_WINDOW) { - Log.d(TAG, "Removing screenshot window"); - } - mWindowManager.removeViewImmediate(decorView); - mDetachRequested = false; - } - if (mAttachRequested && !mDetachRequested) { - mDetachRequested = true; - withWindowAttached(this::removeWindow); - } - + mWindow.removeWindow(); mViewProxy.stopInputListening(); } @@ -759,33 +684,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler { .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; } - /** - * Updates the window focusability. If the window is already showing, then it updates the - * window immediately, otherwise the layout params will be applied when the window is next - * shown. - */ - private void setWindowFocusable(boolean focusable) { - if (DEBUG_WINDOW) { - Log.d(TAG, "setWindowFocusable: " + focusable); - } - int flags = mWindowLayoutParams.flags; - if (focusable) { - mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } else { - mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } - if (mWindowLayoutParams.flags == flags) { - if (DEBUG_WINDOW) { - Log.d(TAG, "setWindowFocusable: skipping, already " + focusable); - } - return; - } - final View decorView = mWindow.peekDecorView(); - if (decorView != null && decorView.isAttachedToWindow()) { - mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); - } - } - private Rect getFullScreenRect() { DisplayMetrics displayMetrics = new DisplayMetrics(); mDisplay.getRealMetrics(displayMetrics); @@ -826,6 +724,6 @@ public class ScreenshotController implements InteractiveScreenshotHandler { * * @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 000000000000..644e12cba6fc --- /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 79e8b879288e..7f8c1463ed1f 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 @@ package com.android.systemui.settings.brightness.ui.viewModel 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 @@ constructor( 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 05c50fe18c8b..15bbef02196a 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 @@ constructor( ) 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/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index 0a092a088e69..16aef6586ee9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -2252,8 +2252,11 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum // 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/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt index bc2377895101..21bbaa5a41f2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt @@ -31,6 +31,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -83,6 +84,7 @@ abstract class ShadeViewProviderModule { scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, layoutInsetController: NotificationInsetsController, sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>, + alternateBouncerDependencies: Provider<AlternateBouncerDependencies>, ): WindowRootView { return if (SceneContainerFlag.isEnabled) { checkNoSceneDuplicates(scenesProvider.get()) @@ -96,6 +98,7 @@ abstract class ShadeViewProviderModule { scenes = scenesProvider.get(), layoutInsetController = layoutInsetController, sceneDataSourceDelegator = sceneDataSourceDelegator.get(), + alternateBouncerDependencies = alternateBouncerDependencies.get(), ) sceneWindowRootView } else { 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 684a4845144e..d64b21f2254f 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 @@ constructor( keyguardRepository: KeyguardRepository, keyguardTransitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, - shadeRepository: ShadeRepository, + private val shadeRepository: ShadeRepository, userSetupRepository: UserSetupRepository, userSwitcherInteractor: UserSwitcherInteractor, private val baseShadeInteractor: BaseShadeInteractor, @@ -114,11 +114,13 @@ constructor( 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 9e221d3d2341..f48e31e1d7eb 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.SysUISingleton 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 @@ constructor( 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 9617b542b427..6a21531d9c06 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.compose.animation.scene.SceneKey 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 @@ class ShadeInteractorSceneContainerImpl 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 @@ constructor( 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 000000000000..068d6a74c8a7 --- /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/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt index 6551854dcb36..566bc166ed40 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 @@ constructor( 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 b2e0cd04687c..03fdfa9aaa6c 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.icu.text.DisplayContext 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.ShadeHeaderClockInteractor 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 @@ constructor( /** 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 @@ constructor( 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) - - applicationScope.launch { updateDateTexts(false) } + 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) + } + + 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 @@ constructor( 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 000000000000..bdc0fdba1dea --- /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 000000000000..3cdff964e26a --- /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 06298efc95a4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shade.ui.viewmodel - -import androidx.lifecycle.LifecycleOwner -import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection -import com.android.compose.animation.scene.UserAction -import com.android.compose.animation.scene.UserActionResult -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.lifecycle.Activatable -import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor -import com.android.systemui.qs.FooterActionsController -import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.qs.ui.adapter.QSSceneAdapter -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneFamilies -import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade -import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.shade.shared.model.ShadeMode -import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor -import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated -import java.util.concurrent.atomic.AtomicBoolean -import javax.inject.Inject -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -/** Models UI state and handles user input for the shade scene. */ -@SysUISingleton -class ShadeSceneViewModel -@Inject -constructor( - val qsSceneAdapter: QSSceneAdapter, - val shadeHeaderViewModel: ShadeHeaderViewModel, - val brightnessMirrorViewModel: BrightnessMirrorViewModel, - val mediaCarouselInteractor: MediaCarouselInteractor, - shadeInteractor: ShadeInteractor, - private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, - private val footerActionsController: FooterActionsController, - private val sceneInteractor: SceneInteractor, - private val unfoldTransitionInteractor: UnfoldTransitionInteractor, -) : Activatable { - val destinationScenes: Flow<Map<UserAction, UserActionResult>> = - combine( - shadeInteractor.shadeMode, - qsSceneAdapter.isCustomizerShowing, - ) { shadeMode, isCustomizerShowing -> - buildMap { - if (!isCustomizerShowing) { - set( - Swipe(SwipeDirection.Up), - UserActionResult( - SceneFamilies.Home, - ToSplitShade.takeIf { shadeMode is ShadeMode.Split } - ) - ) - } - - // TODO(b/330200163) Add an else to be able to collapse the shade while customizing - if (shadeMode is ShadeMode.Single) { - set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings)) - } - } - } - - private val upDestinationSceneKey: Flow<SceneKey?> = - destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene } - - private val _isClickable = MutableStateFlow(false) - /** Whether or not the shade container should be clickable. */ - val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow() - - /** - * Activates the view-model. - * - * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to - * keep its state fresh and/or perform side-effects. - * - * Suspends the caller forever as it will keep doing work until canceled. - * - * **Must be invoked** when the scene becomes the current scene or when it becomes visible - * during a transition (the choice is the responsibility of the parent). Similarly, the work - * must be canceled when the scene stops being visible or the current scene. - */ - override suspend fun activate() { - coroutineScope { - launch { - upDestinationSceneKey - .flatMapLatestConflated { key -> - key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null) - } - .map { it == Scenes.Lockscreen } - .collectLatest { _isClickable.value = it } - } - } - } - - val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode - - val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation - - /** - * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded - * slightly, in pixels. - */ - fun unfoldTranslationX(isOnStartSide: Boolean): Flow<Float> { - return unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide) - } - - /** Notifies that some content in the shade was clicked. */ - fun onContentClicked() { - if (!isClickable.value) { - return - } - - sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked") - } - - private val footerActionsControllerInitialized = AtomicBoolean(false) - - fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { - if (footerActionsControllerInitialized.compareAndSet(false, true)) { - footerActionsController.init() - } - return footerActionsViewModelFactory.create(lifecycleOwner) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index c1eb8bcfa493..5eef8ea1999d 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 @@ public class KeyguardIndicationController { 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/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 1cb59f14626d..9b382e61b2e3 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,16 @@ constructor( * [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 } + } + .distinctUntilChanged() + .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/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index 2537affbb28b..5d3747628e7e 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 @@ -23,7 +23,9 @@ 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 @@ -43,7 +45,8 @@ class SharedNotificationContainerInteractor 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>, @@ -56,16 +59,33 @@ constructor( /** 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 = 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 05415503675c..aa1911e4cd2a 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 @@ constructor( 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/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 428102530334..bfb624a9287b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -33,8 +33,8 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_DELAYED_STACK_FADE_IN import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA -import com.android.systemui.util.kotlin.FlowDumper -import com.android.systemui.util.kotlin.FlowDumperImpl +import com.android.systemui.util.kotlin.ActivatableFlowDumper +import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl import dagger.Lazy import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -55,9 +55,14 @@ constructor( // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released - // while the flag is off, creating this object too early results in a crash keyguardInteractor: Lazy<KeyguardInteractor>, -) : FlowDumper by FlowDumperImpl(dumpManager, "NotificationScrollViewModel"), +) : + ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"), SysUiViewModel() { + override suspend fun onActivated() { + activateFlowDumper() + } + /** * The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning * from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while 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 db91eed68b4c..d179888b569c 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.FeatureFlagsClassic 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.statusbar.notification.stack.shared.model.ShadeScrim 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 @@ constructor( dumpManager: DumpManager, private val interactor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, - private val shadeSceneViewModel: ShadeSceneViewModel, private val headsUpNotificationInteractor: HeadsUpNotificationInteractor, featureFlags: FeatureFlagsClassic, ) : FlowDumperImpl(dumpManager) { @@ -63,20 +60,11 @@ constructor( 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> - get() = 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 b6d58d6a23d9..ada3c52b16ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2353,7 +2353,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing() && mStatusBarKeyguardViewManager.isSecure()) { Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer"); - mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */); + if (SceneContainerFlag.isEnabled()) { + mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */); + } else { + mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */); + } } } } @@ -2985,7 +2989,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onFalse() { // Hides quick settings, bouncer, and quick-quick settings. - mStatusBarKeyguardViewManager.reset(true); + mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true); } }; 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 2d775b74eb32..5486abba9987 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -708,7 +708,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Shows the notification keyguard or the bouncer depending on * {@link #needsFullscreenBouncer()}. */ - protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { + protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) { boolean isDozing = mDozing; if (Flags.simPinRaceConditionOnRestart()) { KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue() @@ -734,8 +734,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); } } - } else { - Log.e(TAG, "Attempted to show the sim bouncer when it is already showing."); + } else if (!isFalsingReset) { + // Falsing resets can cause this to flicker, so don't reset in this case + Log.i(TAG, "Sim bouncer is already showing, issuing a refresh"); + mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); + } } else { mCentralSurfaces.showKeyguard(); @@ -957,6 +960,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void reset(boolean hideBouncerWhenShowing) { + reset(hideBouncerWhenShowing, /* isFalsingReset= */false); + } + + public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) { if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) { final boolean isOccluded = mKeyguardStateController.isOccluded(); // Hide quick settings. @@ -965,10 +972,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (isOccluded && !mDozing) { mCentralSurfaces.hideKeyguard(); if (hideBouncerWhenShowing || needsFullscreenBouncer()) { - hideBouncer(false /* destroyView */); + // We're removing "reset" in the refactor - bouncer will be hidden by the root + // cause of the "reset" calls. + if (!KeyguardWmStateRefactor.isEnabled()) { + hideBouncer(false /* destroyView */); + } } } else { - showBouncerOrKeyguard(hideBouncerWhenShowing); + showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset); } if (hideBouncerWhenShowing) { hideAlternateBouncer(true); 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 4368239c31f0..bd6a1c05ddc9 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 @@ -227,6 +227,15 @@ constructor( callNotificationInfo // This shouldn't happen, but protect against it in case ?: return OngoingCallModel.NoCall + logger.log( + TAG, + LogLevel.DEBUG, + { + bool1 = Flags.statusBarCallChipNotificationIcon() + bool2 = currentInfo.notificationIconView != null + }, + { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" } + ) val icon = if (Flags.statusBarCallChipNotificationIcon()) { currentInfo.notificationIconView @@ -257,6 +266,7 @@ constructor( private fun updateInfoFromNotifModel(notifModel: ActiveNotificationModel?) { if (notifModel == null) { + logger.log(TAG, LogLevel.DEBUG, {}, { "NotifInteractorCallModel: null" }) removeChip() } else if (notifModel.callType != CallType.Ongoing) { logger.log( @@ -267,6 +277,18 @@ constructor( ) removeChip() } else { + logger.log( + TAG, + LogLevel.DEBUG, + { + str1 = notifModel.key + long1 = notifModel.whenTime + str1 = notifModel.callType.name + bool1 = notifModel.statusBarChipIconView != null + }, + { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" } + ) + val newOngoingCallInfo = CallNotificationInfo( notifModel.key, 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 b7531b0e2e0f..44b692fcb786 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt @@ -131,7 +131,7 @@ constructor( 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/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionKeyTutorialScreen.kt new file mode 100644 index 000000000000..f7f26314bb50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionKeyTutorialScreen.kt @@ -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.systemui.touchpad.tutorial.ui.composable + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +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.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent +import androidx.compose.ui.input.key.type +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.composable.TutorialActionState.FINISHED +import com.android.systemui.touchpad.tutorial.ui.composable.TutorialActionState.NOT_STARTED + +@Composable +fun ActionKeyTutorialScreen( + onDoneButtonClicked: () -> Unit, + onBack: () -> Unit, +) { + BackHandler(onBack = onBack) + val screenConfig = buildScreenConfig() + var actionState by remember { mutableStateOf(NOT_STARTED) } + Box( + modifier = + Modifier.fillMaxSize().onKeyEvent { keyEvent: KeyEvent -> + // temporary before we can access Action/Meta key + if (keyEvent.key == Key.AltLeft && keyEvent.type == KeyEventType.KeyUp) { + actionState = FINISHED + } + true + } + ) { + ActionTutorialContent(actionState, onDoneButtonClicked, screenConfig) + } +} + +@Composable +private fun buildScreenConfig() = + TutorialScreenConfig( + colors = rememberScreenColors(), + strings = + TutorialScreenConfig.Strings( + titleResId = R.string.tutorial_action_key_title, + bodyResId = R.string.tutorial_action_key_guidance, + titleSuccessResId = R.string.tutorial_action_key_success_title, + bodySuccessResId = R.string.tutorial_action_key_success_body + ), + animations = + TutorialScreenConfig.Animations( + educationResId = R.raw.action_key_edu, + successResId = R.raw.action_key_success + ) + ) + +@Composable +private fun rememberScreenColors(): TutorialScreenConfig.Colors { + val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim + val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim + val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed + val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant + val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer + val dynamicProperties = + rememberLottieDynamicProperties( + rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim), + rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim), + rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed), + rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant) + ) + val screenColors = + remember(surfaceContainer, dynamicProperties) { + TutorialScreenConfig.Colors( + background = onSecondaryFixed, + successBackground = surfaceContainer, + title = primaryFixedDim, + animationColors = dynamicProperties, + ) + } + return screenColors +} diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionTutorialContent.kt new file mode 100644 index 000000000000..2b7f6742e335 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/ActionTutorialContent.kt @@ -0,0 +1,237 @@ +/* + * 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.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.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.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +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.composable.TutorialActionState.FINISHED +import com.android.systemui.touchpad.tutorial.ui.composable.TutorialActionState.IN_PROGRESS +import com.android.systemui.touchpad.tutorial.ui.composable.TutorialActionState.NOT_STARTED + +enum class TutorialActionState { + NOT_STARTED, + IN_PROGRESS, + FINISHED +} + +@Composable +fun ActionTutorialContent( + actionState: TutorialActionState, + onDoneButtonClicked: () -> Unit, + config: TutorialScreenConfig +) { + val animatedColor by + animateColorAsState( + targetValue = + if (actionState == 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 (actionState == FINISHED) config.strings.titleSuccessResId + else config.strings.titleResId, + titleColor = config.colors.title, + bodyTextId = + if (actionState == FINISHED) config.strings.bodySuccessResId + else config.strings.bodyResId, + modifier = Modifier.weight(1f) + ) + Spacer(modifier = Modifier.width(76.dp)) + TutorialAnimation( + actionState, + 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( + actionState: TutorialActionState, + config: TutorialScreenConfig, + modifier: Modifier = Modifier +) { + Box(modifier = modifier.fillMaxWidth()) { + AnimatedContent( + targetState = actionState, + transitionSpec = { + if (initialState == NOT_STARTED) { + 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 + } + } + ) { state -> + when (state) { + 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/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt index 1b00ae2103f0..5980e1de85b4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt @@ -38,8 +38,8 @@ fun BackGestureTutorialScreen( TutorialScreenConfig.Strings( titleResId = R.string.touchpad_back_gesture_action_title, bodyResId = R.string.touchpad_back_gesture_guidance, - titleSuccessResId = R.string.touchpad_tutorial_gesture_done, - bodySuccessResId = R.string.touchpad_back_gesture_finished + titleSuccessResId = R.string.touchpad_back_gesture_success_title, + bodySuccessResId = R.string.touchpad_back_gesture_success_body ), animations = TutorialScreenConfig.Animations( diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 416c562d212d..dfe9c0df9ab7 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -16,57 +16,18 @@ 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 @@ -81,6 +42,14 @@ interface GestureMonitorProvider { ): TouchpadGestureMonitor } +fun GestureState.toTutorialActionState(): TutorialActionState { + return when (this) { + NOT_STARTED -> TutorialActionState.NOT_STARTED + IN_PROGRESS -> TutorialActionState.IN_PROGRESS + FINISHED -> TutorialActionState.FINISHED + } +} + @Composable fun GestureTutorialScreen( screenConfig: TutorialScreenConfig, @@ -104,7 +73,11 @@ fun GestureTutorialScreen( ) } TouchpadGesturesHandlingBox(gestureHandler, gestureState) { - GestureTutorialContent(gestureState, onDoneButtonClicked, screenConfig) + ActionTutorialContent( + gestureState.toTutorialActionState(), + onDoneButtonClicked, + screenConfig + ) } } @@ -135,165 +108,3 @@ private fun TouchpadGesturesHandlingBox( 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)) - } 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 index 51b14c295275..ed3110c04131 100644 --- 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 @@ -38,8 +38,8 @@ fun HomeGestureTutorialScreen( TutorialScreenConfig.Strings( titleResId = R.string.touchpad_home_gesture_action_title, bodyResId = R.string.touchpad_home_gesture_guidance, - titleSuccessResId = R.string.touchpad_home_gesture_done, - bodySuccessResId = R.string.touchpad_home_gesture_finished + titleSuccessResId = R.string.touchpad_home_gesture_success_title, + bodySuccessResId = R.string.touchpad_home_gesture_success_body ), animations = TutorialScreenConfig.Animations( 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 ad8ab30daa33..48e6397a0a42 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 @@ -27,9 +27,11 @@ import androidx.compose.runtime.getValue import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme +import com.android.systemui.touchpad.tutorial.ui.composable.ActionKeyTutorialScreen 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.ACTION_KEY import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.BACK_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.HOME_GESTURE import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen.TUTORIAL_SELECTION @@ -71,7 +73,7 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U TutorialSelectionScreen( onBackTutorialClicked = { vm.goTo(BACK_GESTURE) }, onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) }, - onActionKeyTutorialClicked = {}, + onActionKeyTutorialClicked = { vm.goTo(ACTION_KEY) }, onDoneButtonClicked = closeTutorial ) BACK_GESTURE -> @@ -84,5 +86,10 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, onBack = { vm.goTo(TUTORIAL_SELECTION) }, ) + ACTION_KEY -> // TODO(b/358105049) move action key tutorial to OOBE flow + ActionKeyTutorialScreen( + onDoneButtonClicked = { vm.goTo(TUTORIAL_SELECTION) }, + onBack = { vm.goTo(TUTORIAL_SELECTION) }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt index 11984af0281a..d3aeaa7e3dca 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialViewModel.kt @@ -55,4 +55,5 @@ enum class Screen { TUTORIAL_SELECTION, BACK_GESTURE, HOME_GESTURE, + ACTION_KEY, } diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java index 56b466249e93..32f2ca6fb696 100644 --- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java @@ -116,7 +116,7 @@ public class CreateUserActivity extends Activity { return mCreateUserDialogController.createDialog( this, this::startActivity, - (mUserCreator.isMultipleAdminEnabled() && mUserCreator.isUserAdmin() + (mUserCreator.canCreateAdminUser() && mUserCreator.isUserAdmin() && !isKeyguardShowing), this::addUserNow, this::finish diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt index 9304a462b6a8..a426da929d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt @@ -19,6 +19,7 @@ import android.app.Dialog import android.content.Context import android.content.pm.UserInfo import android.graphics.drawable.Drawable +import android.multiuser.Flags import android.os.UserManager import com.android.internal.util.UserIcons import com.android.settingslib.users.UserCreatingDialog @@ -91,7 +92,17 @@ constructor( return userManager.isAdminUser } - fun isMultipleAdminEnabled(): Boolean { - return UserManager.isMultipleAdminEnabled() + /** + * Checks if the creation of a new admin user is allowed. + * + * @return `true` if creating a new admin is allowed, `false` otherwise. + */ + fun canCreateAdminUser(): Boolean { + return if (Flags.unicornModeRefactoringForHsumReadOnly()) { + UserManager.isMultipleAdminEnabled() && + !userManager.hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN) + } else { + UserManager.isMultipleAdminEnabled() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt index e17274c435aa..ade6c3df2e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt @@ -19,11 +19,14 @@ package com.android.systemui.util.kotlin import android.util.IndentingPrintWriter import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager +import com.android.systemui.lifecycle.SafeActivatable +import com.android.systemui.lifecycle.SysUiViewModel import com.android.systemui.util.asIndenting import com.android.systemui.util.printCollection import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow @@ -64,19 +67,20 @@ interface FlowDumper : Dumpable { } /** - * An implementation of [FlowDumper]. This be extended directly, or can be used to implement - * [FlowDumper] by delegation. - * - * @param dumpManager if provided, this will be used by the [FlowDumperImpl] to register and - * unregister itself when there is something to dump. - * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this - * class's name will be used. If you're implementing by delegation, you probably want to provide - * this tag to get a meaningful dumpable name. + * The minimal implementation of FlowDumper. The owner must either register this with the + * DumpManager, or else call [dumpFlows] from its own [Dumpable.dump] method. */ -open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = null) : FlowDumper { +open class SimpleFlowDumper : FlowDumper { + private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>() private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>() private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>() + + protected fun isNotEmpty(): Boolean = + stateFlowMap.isNotEmpty() || sharedFlowMap.isNotEmpty() || flowCollectionMap.isNotEmpty() + + protected open fun onMapKeysChanged(added: Boolean) {} + override fun dumpFlows(pw: IndentingPrintWriter) { pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) -> append(key).append('=').println(flow.value) @@ -92,43 +96,62 @@ open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = } } - private val Any.idString: String - get() = Integer.toHexString(System.identityHashCode(this)) - override fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> = flow { val mapKey = dumpName to idString try { collect { flowCollectionMap[mapKey] = it ?: "null" - updateRegistration(required = true) + onMapKeysChanged(added = true) emit(it) } } finally { flowCollectionMap.remove(mapKey) - updateRegistration(required = false) + onMapKeysChanged(added = false) } } override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F { stateFlowMap[dumpName] = this + onMapKeysChanged(added = true) return this } override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F { sharedFlowMap[dumpName] = this + onMapKeysChanged(added = true) return this } - private val dumpManagerName = tag ?: "[$idString] ${javaClass.simpleName}" + protected val Any.idString: String + get() = Integer.toHexString(System.identityHashCode(this)) +} + +/** + * An implementation of [FlowDumper] that registers itself whenever there is something to dump. This + * class is meant to be extended. + * + * @param dumpManager this will be used by the [FlowDumperImpl] to register and unregister itself + * when there is something to dump. + * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this + * class's name will be used. + */ +abstract class FlowDumperImpl( + private val dumpManager: DumpManager, + private val tag: String? = null, +) : SimpleFlowDumper() { + + override fun onMapKeysChanged(added: Boolean) { + updateRegistration(required = added) + } + + private val dumpManagerName = "[$idString] ${tag ?: javaClass.simpleName}" + private var registered = AtomicBoolean(false) + private fun updateRegistration(required: Boolean) { - if (dumpManager == null) return if (required && registered.get()) return synchronized(registered) { - val shouldRegister = - stateFlowMap.isNotEmpty() || - sharedFlowMap.isNotEmpty() || - flowCollectionMap.isNotEmpty() + val shouldRegister = isNotEmpty() val wasRegistered = registered.getAndSet(shouldRegister) if (wasRegistered != shouldRegister) { if (shouldRegister) { @@ -140,3 +163,49 @@ open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = } } } + +/** + * A [FlowDumper] that also has an [activateFlowDumper] suspend function that allows the dumper to + * be registered with the [DumpManager] only when activated, just like + * [Activatable.activate()][com.android.systemui.lifecycle.Activatable.activate]. + */ +interface ActivatableFlowDumper : FlowDumper { + suspend fun activateFlowDumper() +} + +/** + * Implementation of [ActivatableFlowDumper] that only registers when activated. + * + * This is generally used to implement [ActivatableFlowDumper] by delegation, especially for + * [SysUiViewModel] implementations. + * + * @param dumpManager used to automatically register and unregister this instance when activated and + * there is something to dump. + * @param tag the name with which this is dumper registered. + */ +class ActivatableFlowDumperImpl( + private val dumpManager: DumpManager, + tag: String, +) : SimpleFlowDumper(), ActivatableFlowDumper { + + private val registration = + object : SafeActivatable() { + override suspend fun onActivated() { + try { + dumpManager.registerCriticalDumpable( + dumpManagerName, + this@ActivatableFlowDumperImpl + ) + awaitCancellation() + } finally { + dumpManager.unregisterDumpable(dumpManagerName) + } + } + } + + private val dumpManagerName = "[$idString] $tag" + + override suspend fun activateFlowDumper() { + registration.activate() + } +} 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 28ac2c0e8283..055671cf32ca 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 javax.inject.Inject 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 @@ constructor( /** * 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 @@ fun <T> collectFlow( consumer: Consumer<T>, coroutineContext: CoroutineContext = EmptyCoroutineContext, state: Lifecycle.State = Lifecycle.State.CREATED, -) { - view.repeatWhenAttached(coroutineContext) { +): DisposableHandle { + return view.repeatWhenAttached(coroutineContext) { repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java index 816f55db65a0..7fcabe4a4363 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 @@ class GlobalSettingsImpl implements GlobalSettings { 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 f1da27f9cce9..c29648186d54 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 @@ class SecureSettingsImpl implements SecureSettings { 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 0ee997e4549d..82f41a7fd154 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 @@ interface SettingsProxy { * @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 @@ interface SettingsProxy { * @return true if the value was set, false on database errors. * @see .resetToDefaults */ - fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean + fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean /** * Convenience function for retrieving a single secure settings value as an integer. Note that diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java index 1e8035734a36..e670b2c2edd0 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 @@ class SystemSettingsImpl implements SystemSettings { 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 9ae8f03479cf..8e3b813a2a82 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 @@ interface UserSettingsProxy : SettingsProxy { * @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/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt index e46ce2699beb..24fb001a1b6d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.data.repository import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState import java.io.PrintWriter import javax.inject.Inject @@ -27,10 +28,15 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -private const val TAG = "VolumePanelGlobalState" +private const val TAG = "VolumePanelGlobalStateRepository" @SysUISingleton -class VolumePanelGlobalStateRepository @Inject constructor(dumpManager: DumpManager) : Dumpable { +class VolumePanelGlobalStateRepository +@Inject +constructor( + dumpManager: DumpManager, + private val logger: VolumePanelLogger, +) : Dumpable { private val mutableGlobalState = MutableStateFlow( @@ -48,6 +54,7 @@ class VolumePanelGlobalStateRepository @Inject constructor(dumpManager: DumpMana update: (currentState: VolumePanelGlobalState) -> VolumePanelGlobalState ) { mutableGlobalState.update(update) + logger.onVolumePanelGlobalStateChanged(mutableGlobalState.value) } override fun dump(pw: PrintWriter, args: Array<out String>) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt index 5301b008bab7..9de862a814d6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.domain.interactor import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.domain.model.ComponentModel +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import javax.inject.Inject import javax.inject.Provider @@ -26,8 +27,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn interface ComponentsInteractor { @@ -45,6 +50,7 @@ constructor( enabledComponents: Collection<VolumePanelComponentKey>, defaultCriteria: Provider<ComponentAvailabilityCriteria>, @VolumePanelScope coroutineScope: CoroutineScope, + private val logger: VolumePanelLogger, private val criteriaByKey: Map< VolumePanelComponentKey, @@ -57,12 +63,18 @@ constructor( combine( enabledComponents.map { componentKey -> val componentCriteria = (criteriaByKey[componentKey] ?: defaultCriteria).get() - componentCriteria.isAvailable().map { isAvailable -> - ComponentModel(componentKey, isAvailable = isAvailable) - } + componentCriteria + .isAvailable() + .distinctUntilChanged() + .conflate() + .onEach { logger.onComponentAvailabilityChanged(componentKey, it) } + .map { isAvailable -> + ComponentModel(componentKey, isAvailable = isAvailable) + } } ) { it.asList() } - .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + .stateIn(coroutineScope, SharingStarted.Eagerly, null) + .filterNotNull() } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt index cc513b5d820c..276326cbf430 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt @@ -20,15 +20,41 @@ import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.VolumeLog -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.VolumePanelGlobalState +import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState import javax.inject.Inject private const val TAG = "SysUI_VolumePanel" /** Logs events related to the Volume Panel. */ -@VolumePanelScope class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) { + fun onVolumePanelStateChanged(state: VolumePanelState) { + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = state.toString() }, { "State changed: $str1" }) + } + + fun onComponentAvailabilityChanged(key: VolumePanelComponentKey, isAvailable: Boolean) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = isAvailable + }, + { "$str1 isAvailable=$bool1" } + ) + } + + fun onVolumePanelGlobalStateChanged(globalState: VolumePanelGlobalState) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { bool1 = globalState.isVisible }, + { "Global state changed: isVisible=$bool1" } + ) + } + fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt index 1c51236689d8..a06d3e3c6785 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayout.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.ui.layout import com.android.systemui.volume.panel.ui.viewmodel.ComponentState +import com.android.systemui.volume.panel.ui.viewmodel.toLogString /** Represents components grouping into the layout. */ data class ComponentsLayout( @@ -29,3 +30,12 @@ data class ComponentsLayout( /** This is a separated entity that is always visible on the bottom of the Volume Panel. */ val bottomBarComponent: ComponentState, ) + +fun ComponentsLayout.toLogString(): String { + return "(" + + " headerComponents=${headerComponents.joinToString { it.toLogString() }}" + + " contentComponents=${contentComponents.joinToString { it.toLogString() }}" + + " footerComponents=${footerComponents.joinToString { it.toLogString() }}" + + " bottomBarComponent=${bottomBarComponent.toLogString()}" + + " )" +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt index 5f4dbfb4235e..41c80fa58527 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/ComponentState.kt @@ -32,3 +32,5 @@ data class ComponentState( val component: VolumePanelUiComponent, val isVisible: Boolean, ) + +fun ComponentState.toLogString(): String = "$key:visible=$isVisible" diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt index f495a02f6cc7..2f60c4b29a81 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt @@ -19,23 +19,30 @@ package com.android.systemui.volume.panel.ui.viewmodel import android.content.Context import android.content.IntentFilter import android.content.res.Resources +import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dump.DumpManager import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged +import com.android.systemui.util.kotlin.launchAndDispose import com.android.systemui.volume.VolumePanelDialogReceiver import com.android.systemui.volume.panel.dagger.VolumePanelComponent import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory import com.android.systemui.volume.panel.domain.VolumePanelStartable import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor import com.android.systemui.volume.panel.domain.interactor.VolumePanelGlobalStateInteractor +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.ui.composable.ComponentsFactory import com.android.systemui.volume.panel.ui.layout.ComponentsLayout import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager +import com.android.systemui.volume.panel.ui.layout.toLogString +import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -43,19 +50,23 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +private const val TAG = "VolumePanelViewModel" + // Can't inject a constructor here because VolumePanelComponent provides this view model for its // components. +@OptIn(ExperimentalCoroutinesApi::class) class VolumePanelViewModel( resources: Resources, coroutineScope: CoroutineScope, daggerComponentFactory: VolumePanelComponentFactory, configurationController: ConfigurationController, broadcastDispatcher: BroadcastDispatcher, + private val dumpManager: DumpManager, + private val logger: VolumePanelLogger, private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor, -) { +) : Dumpable { private val volumePanelComponent: VolumePanelComponent = daggerComponentFactory.create(this, coroutineScope) @@ -77,9 +88,10 @@ class VolumePanelViewModel( .onStart { emit(resources.configuration) } .map { configuration -> VolumePanelState( - orientation = configuration.orientation, - isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen), - ) + orientation = configuration.orientation, + isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen), + ) + .also { logger.onVolumePanelStateChanged(it) } } .stateIn( scope, @@ -89,7 +101,7 @@ class VolumePanelViewModel( isLargeScreen = resources.getBoolean(R.bool.volume_panel_is_large_screen) ), ) - val componentsLayout: Flow<ComponentsLayout> = + val componentsLayout: StateFlow<ComponentsLayout?> = combine( componentsInteractor.components, volumePanelState, @@ -104,13 +116,18 @@ class VolumePanelViewModel( } componentsLayoutManager.layout(scope, componentStates) } - .shareIn( + .stateIn( scope, SharingStarted.Eagerly, - replay = 1, + null, ) init { + scope.launchAndDispose { + dumpManager.registerNormalDumpable(TAG, this) + DisposableHandle { dumpManager.unregisterDumpable(TAG) } + } + volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start) broadcastDispatcher .broadcastFlow(IntentFilter(VolumePanelDialogReceiver.DISMISS_ACTION)) @@ -122,6 +139,13 @@ class VolumePanelViewModel( volumePanelGlobalStateInteractor.setVisible(false) } + override fun dump(pw: PrintWriter, args: Array<out String>) { + with(pw) { + println("volumePanelState=${volumePanelState.value}") + println("componentsLayout=${componentsLayout.value?.toLogString()}") + } + } + class Factory @Inject constructor( @@ -129,6 +153,8 @@ class VolumePanelViewModel( private val daggerComponentFactory: VolumePanelComponentFactory, private val configurationController: ConfigurationController, private val broadcastDispatcher: BroadcastDispatcher, + private val dumpManager: DumpManager, + private val logger: VolumePanelLogger, private val volumePanelGlobalStateInteractor: VolumePanelGlobalStateInteractor, ) { @@ -139,6 +165,8 @@ class VolumePanelViewModel( daggerComponentFactory, configurationController, broadcastDispatcher, + dumpManager, + logger, volumePanelGlobalStateInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java new file mode 100644 index 000000000000..2ac5d105ba99 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java @@ -0,0 +1,285 @@ +/* + * 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.accessibility.hearingaid; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import static java.util.Collections.emptyList; + +import android.bluetooth.BluetoothCsipSetCoordinator; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHapClient; +import android.bluetooth.BluetoothHapPresetInfo; +import android.testing.TestableLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HapClientProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.SysuiTestCase; + +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.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; +import java.util.concurrent.Executor; + +/** Tests for {@link HearingDevicesPresetsController}. */ +@RunWith(AndroidJUnit4.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class HearingDevicesPresetsControllerTest extends SysuiTestCase { + + private static final int TEST_PRESET_INDEX = 1; + private static final String TEST_PRESET_NAME = "test_preset"; + private static final int TEST_HAP_GROUP_ID = 1; + private static final int TEST_REASON = 1024; + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private HapClientProfile mHapClientProfile; + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private CachedBluetoothDevice mSubCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private BluetoothDevice mSubBluetoothDevice; + + @Mock + private HearingDevicesPresetsController.PresetCallback mCallback; + + private HearingDevicesPresetsController mController; + + @Before + public void setUp() { + when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); + when(mHapClientProfile.isProfileReady()).thenReturn(true); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice); + when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice); + + mController = new HearingDevicesPresetsController(mProfileManager, mCallback); + } + + @Test + public void onServiceConnected_callExpectedCallback() { + mController.onServiceConnected(); + + verify(mHapClientProfile).registerCallback(any(Executor.class), + any(BluetoothHapClient.Callback.class)); + verify(mCallback).onPresetInfoUpdated(anyList(), anyInt()); + } + + @Test + public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() { + when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList()); + mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + + assertThat(mController.getAllPresetInfo()).isEmpty(); + } + + @Test + public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + + assertThat(mController.getAllPresetInfo()).isEmpty(); + } + + @Test + public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + + assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo); + } + + @Test + public void getActivePresetIndex_getExpectedIndex() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn( + TEST_PRESET_INDEX); + + assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX); + } + + @Test + public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn( + TEST_PRESET_INDEX); + + mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON); + + verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX)); + } + + @Test + public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() { + setValidHearingDeviceSupportHap(); + BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true); + when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn( + List.of(hapPresetInfo)); + when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn( + TEST_PRESET_INDEX); + + mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON); + + verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX); + } + + @Test + public void onPresetSelectionFailed_callOnPresetCommandFailed() { + setValidHearingDeviceSupportHap(); + + mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON); + + verify(mCallback).onPresetCommandFailed(TEST_REASON); + } + + @Test + public void onSetPresetNameFailed_callOnPresetCommandFailed() { + setValidHearingDeviceSupportHap(); + + mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON); + + verify(mCallback).onPresetCommandFailed(TEST_REASON); + } + + @Test + public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() { + setValidHearingDeviceSupportHap(); + mController.selectPreset(TEST_PRESET_INDEX); + Mockito.reset(mHapClientProfile); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON); + + + verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX); + } + + @Test + public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() { + setValidHearingDeviceSupportHap(); + + mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON); + + verify(mCallback).onPresetCommandFailed(TEST_REASON); + } + + @Test + public void registerHapCallback_callHapRegisterCallback() { + mController.registerHapCallback(); + + verify(mHapClientProfile).registerCallback(any(Executor.class), + any(BluetoothHapClient.Callback.class)); + } + + @Test + public void unregisterHapCallback_callHapUnregisterCallback() { + mController.unregisterHapCallback(); + + verify(mHapClientProfile).unregisterCallback(any(BluetoothHapClient.Callback.class)); + } + + @Test + public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.selectPreset(TEST_PRESET_INDEX); + + verify(mHapClientProfile).selectPresetForGroup(TEST_HAP_GROUP_ID, TEST_PRESET_INDEX); + } + + @Test + public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID); + + mController.selectPreset(TEST_PRESET_INDEX); + + verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX); + } + + @Test + public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() { + setValidHearingDeviceSupportHap(); + when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false); + when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID); + + mController.selectPreset(TEST_PRESET_INDEX); + + verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX); + verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX); + } + + private BluetoothHapPresetInfo getHapPresetInfo(boolean available) { + BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class); + when(info.getName()).thenReturn(TEST_PRESET_NAME); + when(info.getIndex()).thenReturn(TEST_PRESET_INDEX); + when(info.isAvailable()).thenReturn(available); + return info; + } + + private void setValidHearingDeviceSupportHap() { + LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class); + List<LocalBluetoothProfile> profiles = List.of(hapClientProfile); + when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles); + + mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice); + } +} 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 5600b87280ad..a18d272b8fe3 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 @@ public class TouchMonitorTest extends SysuiTestCase { } @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/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt index e44bc7b43fb1..d8e96bc23b25 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 @@ -51,14 +51,14 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { @Test fun showPrimaryBouncer() = testScope.runTest { - underTest.showPrimaryBouncer() + underTest.onTapped() verify(statusBarKeyguardViewManager).showPrimaryBouncer(any()) } @Test fun hideAlternateBouncer() = testScope.runTest { - underTest.hideAlternateBouncer() + underTest.onRemovedFromWindow() verify(statusBarKeyguardViewManager).hideAlternateBouncer(any()) } @@ -102,34 +102,6 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { } @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 77977f3f1115..24bea2ce51c7 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 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { 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 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { underTest = KeyguardQuickAffordancesCombinedViewModel( + applicationScope = testScope.backgroundScope, quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt index b337ccfda772..8fbd3c8b7ebf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt @@ -24,7 +24,6 @@ import android.view.View import android.view.ViewGroup import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX import com.android.systemui.Flags.FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler @@ -141,27 +140,13 @@ class MediaProjectionRecentsViewControllerTest : SysuiTestCase() { } @Test - fun onRecentAppClicked_fullScreenTaskInForeground_flagOff_usesScaleUpAnimation() { - mSetFlagsRule.disableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX) - - controller.onRecentAppClicked(fullScreenTask, taskView) - - assertThat(getStartedTaskActivityOptions(fullScreenTask.taskId).animationType) - .isEqualTo(ActivityOptions.ANIM_SCALE_UP) - } - - @Test - fun onRecentAppClicked_fullScreenTaskInForeground_flagOn_usesDefaultAnimation() { - mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX) + fun onRecentAppClicked_fullScreenTaskInForeground_usesDefaultAnimation() { assertForegroundTaskUsesDefaultCloseAnimation(fullScreenTask) } @Test fun onRecentAppClicked_splitScreenTaskInForeground_flagOn_usesDefaultAnimation() { - mSetFlagsRule.enableFlags( - FLAG_PSS_APP_SELECTOR_ABRUPT_EXIT_FIX, - FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN - ) + mSetFlagsRule.enableFlags(FLAG_PSS_APP_SELECTOR_RECENTS_SPLIT_SCREEN) assertForegroundTaskUsesDefaultCloseAnimation(splitScreenTask) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt index c57aa369490b..04ef1be9c057 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger import com.android.systemui.res.R import com.android.systemui.statusbar.phone.AlertDialogWithDelegate import com.android.systemui.statusbar.phone.SystemUIDialog +import com.google.common.truth.Truth.assertThat import kotlin.test.assertEquals import org.junit.After import org.junit.Test @@ -44,8 +45,10 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { 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 @@ -115,6 +118,36 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { assertEquals(context.getString(resIdFullScreen), secondOptionText) } + @Test + fun startButtonText_entireScreenSelected() { + setUpAndShowDialog() + onSpinnerItemSelected(ENTIRE_SCREEN) + + val startButtonText = dialog.requireViewById<TextView>(android.R.id.button1).text + + assertThat(startButtonText) + .isEqualTo( + context.getString( + R.string.media_projection_entry_app_permission_dialog_continue_entire_screen + ) + ) + } + + @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, @@ -142,4 +175,10 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() { 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 a52ab0c690a4..04d140c458e8 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 @@ -78,6 +78,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; @@ -214,6 +215,8 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private WindowManager mWindowManager; @Mock + private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager; + @Mock private TelecomManager mTelecomManager; @Mock private InputMethodManager mInputMethodManager; @@ -619,6 +622,7 @@ public class NavigationBarTest extends SysuiTestCase { null, context, mWindowManager, + mViewCaptureAwareWindowManager, () -> mAssistManager, mock(AccessibilityManager.class), deviceProvisionedController, 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 90e0dd80c55c..0c2b59fed078 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 @@ class UserSettingObserverTest : SysuiTestCase() { 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 @@ class UserSettingObserverTest : SysuiTestCase() { @Before fun setUp() { testableLooper = TestableLooper.get(this) - secureSettings = FakeSettings() + secureSettings = kosmos.fakeSettings setting = object : @@ -76,92 +100,107 @@ class UserSettingObserverTest : SysuiTestCase() { @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) - - // Check default value if setting is not set - setting.isListening = true - 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 + setListening(true) + assertThat(setting.value).isEqualTo(DEFAULT_VALUE) + } + + fun setListening(listening: Boolean) { + setting.isListening = listening + testScope.runCurrent() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 79c206c1a838..3f550ca27868 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.doNothing; 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 @@ public class RecordingServiceTest extends SysuiTestCase { @Mock private ScreenMediaRecorder mScreenMediaRecorder; @Mock - private Notification mNotification; - @Mock private Executor mExecutor; @Mock private Handler mHandler; @@ -124,10 +128,6 @@ public class RecordingServiceTest extends SysuiTestCase { // Mock notifications doNothing().when(mRecordingService).createRecordingNotification(); - doReturn(mNotification).when(mRecordingService).createProcessingNotification(); - doReturn(mNotification).when(mRecordingService).createSaveNotification(any()); - doNothing().when(mRecordingService).createErrorStartingNotification(); - doNothing().when(mRecordingService).createErrorSavingNotification(); doNothing().when(mRecordingService).showErrorToast(anyInt()); doNothing().when(mRecordingService).stopForeground(anyInt()); @@ -228,6 +228,33 @@ public class RecordingServiceTest extends SysuiTestCase { } @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(); @@ -235,7 +262,11 @@ public class RecordingServiceTest extends SysuiTestCase { mRecordingService.onStopped(); - verify(mRecordingService).createErrorSavingNotification(); + 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 2803035f1b82..8125ef55f4af 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.KeyguardStateController; 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new SharedNotificationContainerInteractor( new FakeConfigurationRepository(), mContext, - new ResourcesSplitShadeStateController(), + () -> splitShadeStateController, + () -> mShadeInteractor, mKeyguardInteractor, deviceEntryUdfpsInteractor, () -> mLargeScreenHeaderHelper 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 e57382de2edd..505f7997ef1c 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 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - splitShadeStateController, + () -> splitShadeStateController, + () -> mShadeInteractor, keyguardInteractor, deviceEntryUdfpsInteractor, () -> mLargeScreenHeaderHelper), @@ -266,6 +267,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { 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 e7db4690c2c3..2e9d6e85d0aa 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.ACTION_UP; 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 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl @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 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl } @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/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 80011dcab1cd..a75d7b23a92c 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 android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIME 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 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll 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/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt index 3f28164709fd..491919a16a4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel +import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor @@ -78,7 +79,7 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper -@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME) +@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME, LockscreenOtpRedaction.FLAG_NAME) class NotificationRowContentBinderImplTest : SysuiTestCase() { private lateinit var notificationInflater: NotificationRowContentBinderImpl private lateinit var builder: Notification.Builder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index af5e60e9cd01..9b611057c059 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -1068,7 +1068,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { public void testShowBouncerOrKeyguard_needsFullScreen() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); - mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false); verify(mCentralSurfaces).hideKeyguard(); verify(mPrimaryBouncerInteractor).show(true); } @@ -1084,7 +1084,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(KeyguardState.LOCKSCREEN); reset(mCentralSurfaces); - mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false); verify(mPrimaryBouncerInteractor).show(true); verify(mCentralSurfaces).showKeyguard(); } @@ -1092,11 +1092,26 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test @DisableSceneContainer public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() { + boolean isFalsingReset = false; when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( KeyguardSecurityModel.SecurityMode.SimPin); when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); - mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset); verify(mCentralSurfaces, never()).hideKeyguard(); + verify(mPrimaryBouncerInteractor).show(true); + } + + @Test + @DisableSceneContainer + public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() { + boolean isFalsingReset = true; + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPin); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset); + verify(mCentralSurfaces, never()).hideKeyguard(); + + // Do not refresh the full screen bouncer if the call is from falsing verify(mPrimaryBouncerInteractor, never()).show(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt index b0acd0386870..2e6d0fc847bb 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 @@ class SettingsProxyTest : SysuiTestCase() { 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 @@ class SettingsProxyTest : SysuiTestCase() { 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 eaeece9c293e..00b8cd04bdaf 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 @@ class UserSettingsProxyTest : SysuiTestCase() { ) : 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 @@ class UserSettingsProxyTest : SysuiTestCase() { override fun putString( name: String, - value: String, + value: String?, overrideableByRestore: Boolean ): Boolean { userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value) @@ -586,22 +586,22 @@ class UserSettingsProxyTest : SysuiTestCase() { 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 185deea31747..a61233ad18b9 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/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 727de9e95872..4571c19d101a 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 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { 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 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _dozeTimeTick.value = millis } + override fun showDismissibleKeyguard() { + showDismissibleKeyguard.value = showDismissibleKeyguard.value + 1 + } + override fun setLastDozeTapToWakePosition(position: Point) { _lastDozeTapToWakePosition.value = position } @@ -216,6 +222,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override fun setDreaming(isDreaming: Boolean) { _isDreaming.value = isDreaming + // Intentionally set both for testing, to avoid races with merge() in the interactor that + // would make testing difficult + _isDreamingWithOverlay.value = isDreaming } fun setDreamingWithOverlay(isDreaming: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt index c5da10e59369..b68d6a0510d5 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 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by fromAodTransitionInteractor = { fromAodTransitionInteractor }, fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor }, fromDozingTransitionInteractor = { fromDozingTransitionInteractor }, + fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor }, sceneInteractor = sceneInteractor ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt index 2919d3f575c6..1e95fc12bdb5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.binder import android.content.applicationContext -import android.view.layoutInflater import android.view.mockedLayoutInflater import android.view.windowManager import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor @@ -25,7 +24,6 @@ import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel -import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel @@ -50,10 +48,10 @@ val Kosmos.alternateBouncerViewBinder by alternateBouncerDependencies = { alternateBouncerDependencies }, windowManager = { windowManager }, layoutInflater = { mockedLayoutInflater }, - dismissCallbackRegistry = dismissCallbackRegistry, ) } +@ExperimentalCoroutinesApi private val Kosmos.alternateBouncerDependencies by Kosmos.Fixture { AlternateBouncerDependencies( @@ -69,6 +67,7 @@ private val Kosmos.alternateBouncerDependencies by ) } +@ExperimentalCoroutinesApi private val Kosmos.alternateBouncerUdfpsIconViewModel by Kosmos.Fixture { AlternateBouncerUdfpsIconViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt index bdd4afa3da7d..29583153ccc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt @@ -18,6 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -28,5 +30,7 @@ val Kosmos.alternateBouncerViewModel by Fixture { AlternateBouncerViewModel( statusBarKeyguardViewManager = statusBarKeyguardViewManager, keyguardTransitionInteractor = keyguardTransitionInteractor, + dismissCallbackRegistry = dismissCallbackRegistry, + alternateBouncerInteractor = { alternateBouncerInteractor }, ) } 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 82860fc52045..b9443bcaf650 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 @@ val Kosmos.keyguardRootViewModel by Fixture { communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, + alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, alternateBouncerToLockscreenTransitionViewModel = alternateBouncerToLockscreenTransitionViewModel, 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 index 299b22ef963f..5d70ed6a634c 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 @@ 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 -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/QuickSettingsShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt new file mode 100644 index 000000000000..5ad5cb28e549 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.qs.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModelFactory +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.quickSettingsShadeSceneContentViewModel: QuickSettingsShadeSceneContentViewModel by + Kosmos.Fixture { + QuickSettingsShadeSceneContentViewModel( + overlayShadeViewModelFactory = overlayShadeViewModelFactory, + quickSettingsContainerViewModel = quickSettingsContainerViewModel, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index 8e76a0bf5a13..53b6a2ee226b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.startable import com.android.internal.logging.uiEventLogger import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.falsingCollector @@ -80,5 +81,6 @@ val Kosmos.sceneContainerStartable by Fixture { keyguardEnabledInteractor = keyguardEnabledInteractor, dismissCallbackRegistry = dismissCallbackRegistry, statusBarStateController = sysuiStatusBarStateController, + alternateBouncerInteractor = alternateBouncerInteractor, ) } 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 8fb370caee09..32a561474b4d 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 @@ package com.android.systemui.settings.brightness.ui.viewmodel 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 ea02d0c7ac9a..6d488d21301e 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 @@ package com.android.systemui.shade 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 @@ class ShadeTestUtil constructor(val delegate: ShadeTestUtilDelegate) { 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 @@ interface ShadeTestUtilDelegate { 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 @@ class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: Fak 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 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen 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 9eeb345bde0a..a1551e095f24 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 @@ var Kosmos.shadeTestUtil: ShadeTestUtil by 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 bfd6614a2272..54208b9cdaef 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 @@ val Kosmos.shadeInteractorSceneContainerImpl by 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 index 72a80d480288..9bf4756f53b0 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 000000000000..92401024c91f --- /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/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt index 8d4d54749086..00f1526f6cd4 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 0e21698ef271..7eb9f3472482 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 @@ package com.android.systemui.shade.ui.viewmodel 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 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.mobileIconsVi val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by Kosmos.Fixture { ShadeHeaderViewModel( - applicationScope = applicationCoroutineScope, context = applicationContext, activityStarter = activityStarter, sceneInteractor = sceneInteractor, @@ -43,3 +41,12 @@ val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by 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/ShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt new file mode 100644 index 000000000000..2387aa856fe6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt @@ -0,0 +1,29 @@ +/* + * 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.kosmos.Kosmos.Fixture +import com.android.systemui.qs.ui.adapter.qsSceneAdapter +import com.android.systemui.shade.domain.interactor.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/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt index 2c5a0f4d31bc..7097d3130aa0 100644 --- 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/ShadeSceneContentViewModelKosmos.kt @@ -16,27 +16,29 @@ 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.brightnessMirrorViewModel +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.shadeSceneViewModel: ShadeSceneViewModel by - Kosmos.Fixture { - ShadeSceneViewModel( - shadeHeaderViewModel = shadeHeaderViewModel, - qsSceneAdapter = qsSceneAdapter, - brightnessMirrorViewModel = brightnessMirrorViewModel, - mediaCarouselInteractor = mediaCarouselInteractor, - shadeInteractor = shadeInteractor, - footerActionsViewModelFactory = footerActionsViewModelFactory, - footerActionsController = footerActionsController, - sceneInteractor = sceneInteractor, - unfoldTransitionInteractor = 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/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt index 8909d751227a..3234e66024a8 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.common.ui.data.repository.configurationRepository 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 @@ val Kosmos.sharedNotificationContainerInteractor by 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/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index afb8acb3d819..20dc668e4ff6 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.flags.featureFlagsClassic 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 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture { dumpManager = dumpManager, interactor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, - shadeSceneViewModel = shadeSceneViewModel, headsUpNotificationInteractor = headsUpNotificationInteractor, featureFlags = featureFlagsClassic, ) 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 d1174667648c..000000000000 --- 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 000000000000..e5d113be7ca2 --- /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/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt index 2ba1211a9bdb..0b438d183544 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/data/repository/VolumePanelGlobalStateRepositoryKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.data.repository import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.shared.volumePanelLogger val Kosmos.volumePanelGlobalStateRepository by - Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager) } + Kosmos.Fixture { VolumePanelGlobalStateRepository(dumpManager, volumePanelLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt index a18f498e5441..3804a9f21080 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt @@ -28,6 +28,7 @@ import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.domain.defaultCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey import com.android.systemui.volume.panel.ui.composable.enabledComponents +import com.android.systemui.volume.shared.volumePanelLogger import javax.inject.Provider var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by @@ -50,6 +51,7 @@ var Kosmos.componentsInteractor: ComponentsInteractor by enabledComponents, { defaultCriteria }, testScope.backgroundScope, + volumePanelLogger, criteriaByKey, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt index 34a008f92518..c4fb9e486c4d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt @@ -18,17 +18,19 @@ package com.android.systemui.volume.panel.ui.viewmodel import android.content.applicationContext import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory import com.android.systemui.volume.panel.domain.VolumePanelStartable import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor +import com.android.systemui.volume.shared.volumePanelLogger var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() } var Kosmos.volumePanelViewModel: VolumePanelViewModel by - Kosmos.Fixture { volumePanelViewModelFactory.create(testScope.backgroundScope) } + Kosmos.Fixture { volumePanelViewModelFactory.create(applicationCoroutineScope) } val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by Kosmos.Fixture { @@ -37,6 +39,8 @@ val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by volumePanelComponentFactory, configurationController, broadcastDispatcher, + dumpManager, + volumePanelLogger, volumePanelGlobalStateInteractor, ) } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 4faf03c395f3..615034338c6b 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -280,10 +280,10 @@ sh_test_host { src: "scripts/ravenwood-stats-checker.sh", test_suites: ["general-tests"], data: [ - ":hoststubgen_framework-minus-apex_stats.csv", - ":hoststubgen_framework-minus-apex_apis.csv", - ":hoststubgen_framework-minus-apex_keep_all.txt", - ":hoststubgen_framework-minus-apex_dump.txt", + ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_stats.csv}", + ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_apis.csv}", + ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_keep_all.txt}", + ":framework-minus-apex.ravenwood-base_all{hoststubgen_framework-minus-apex_dump.txt}", ":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}", ":services.core.ravenwood-base{hoststubgen_services.core_apis.csv}", ":services.core.ravenwood-base{hoststubgen_services.core_keep_all.txt}", diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index 68f185eba42c..cc9b70e387e8 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -281,6 +281,12 @@ android.os.connectivity.WifiBatteryStats com.android.server.LocalServices +com.android.internal.graphics.cam.Cam +com.android.internal.graphics.cam.CamUtils +com.android.internal.graphics.cam.Frame +com.android.internal.graphics.cam.HctSolver +com.android.internal.graphics.ColorUtils + com.android.internal.util.BitUtils com.android.internal.util.BitwiseInputStream com.android.internal.util.BitwiseOutputStream diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java index 56da231ad31a..2ce5c2bc3790 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 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation 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 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation 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 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation 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/appfunctions/OWNERS b/services/appfunctions/OWNERS new file mode 100644 index 000000000000..b3108944a3ce --- /dev/null +++ b/services/appfunctions/OWNERS @@ -0,0 +1 @@ +include /core/java/android/app/appfunctions/OWNERS diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 2e1416b2887b..d4f729cfbaf6 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -6962,7 +6962,8 @@ public final class ActiveServices { } private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses, - boolean evenPersistent, boolean doit, ArrayMap<ComponentName, ServiceRecord> services) { + boolean evenPersistent, boolean doit, int minOomAdj, + ArrayMap<ComponentName, ServiceRecord> services) { boolean didSomething = false; for (int i = services.size() - 1; i >= 0; i--) { ServiceRecord service = services.valueAt(i); @@ -6970,6 +6971,11 @@ public final class ActiveServices { || (service.packageName.equals(packageName) && (filterByClasses == null || filterByClasses.contains(service.name.getClassName()))); + if (service.app != null && service.app.mState.getCurAdj() < minOomAdj) { + Slog.i(TAG, "Skip force stopping service " + service + + ": below minimum oom adj level"); + continue; + } if (sameComponent && (service.app == null || evenPersistent || !service.app.isPersistent())) { if (!doit) { @@ -6993,6 +6999,12 @@ public final class ActiveServices { boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses, int userId, boolean evenPersistent, boolean fullStop, boolean doit) { + return bringDownDisabledPackageServicesLocked(packageName, filterByClasses, userId, + evenPersistent, fullStop, doit, ProcessList.INVALID_ADJ); + } + + boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses, + int userId, boolean evenPersistent, boolean fullStop, boolean doit, int minOomAdj) { boolean didSomething = false; if (mTmpCollectionResults != null) { @@ -7002,7 +7014,8 @@ public final class ActiveServices { if (userId == UserHandle.USER_ALL) { for (int i = mServiceMap.size() - 1; i >= 0; i--) { didSomething |= collectPackageServicesLocked(packageName, filterByClasses, - evenPersistent, doit, mServiceMap.valueAt(i).mServicesByInstanceName); + evenPersistent, doit, minOomAdj, + mServiceMap.valueAt(i).mServicesByInstanceName); if (!doit && didSomething) { return true; } @@ -7015,7 +7028,7 @@ public final class ActiveServices { if (smap != null) { ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName; didSomething = collectPackageServicesLocked(packageName, filterByClasses, - evenPersistent, doit, items); + evenPersistent, doit, minOomAdj, items); } if (doit && filterByClasses == null) { forceStopPackageLocked(packageName, userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9d8f3374d6ad..4a18cb1f5ed8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -4377,6 +4377,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") + final boolean forceStopUserPackagesLocked(int userId, String reasonString, + boolean evenImportantServices) { + int minOomAdj = evenImportantServices ? ProcessList.INVALID_ADJ + : ProcessList.FOREGROUND_APP_ADJ; + return forceStopPackageInternalLocked(null, -1, false, false, + true, false, false, false, userId, reasonString, + ApplicationExitInfo.REASON_USER_STOPPED, minOomAdj); + } + + @GuardedBy("this") final boolean forceStopPackageLocked(String packageName, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, @@ -4385,7 +4395,6 @@ public class ActivityManagerService extends IActivityManager.Stub : ApplicationExitInfo.REASON_USER_REQUESTED; return forceStopPackageLocked(packageName, appId, callerWillRestart, purgeCache, doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString, reason); - } @GuardedBy("this") @@ -4393,6 +4402,16 @@ public class ActivityManagerService extends IActivityManager.Stub boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, int userId, String reasonString, int reason) { + return forceStopPackageInternalLocked(packageName, appId, callerWillRestart, purgeCache, + doit, evenPersistent, uninstalling, packageStateStopped, userId, reasonString, + reason, ProcessList.INVALID_ADJ); + } + + @GuardedBy("this") + private boolean forceStopPackageInternalLocked(String packageName, int appId, + boolean callerWillRestart, boolean purgeCache, boolean doit, + boolean evenPersistent, boolean uninstalling, boolean packageStateStopped, + int userId, String reasonString, int reason, int minOomAdj) { int i; if (userId == UserHandle.USER_ALL && packageName == null) { @@ -4431,7 +4450,7 @@ public class ActivityManagerService extends IActivityManager.Stub } didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId, - ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit, + minOomAdj, callerWillRestart, false /* allowRestart */, doit, evenPersistent, true /* setRemoved */, uninstalling, reason, subReason, @@ -4440,7 +4459,8 @@ public class ActivityManagerService extends IActivityManager.Stub } if (mServices.bringDownDisabledPackageServicesLocked( - packageName, null /* filterByClasses */, userId, evenPersistent, true, doit)) { + packageName, null /* filterByClasses */, userId, evenPersistent, + true, doit, minOomAdj)) { if (!doit) { return true; } @@ -19872,6 +19892,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) { + return mUserController.isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId); + } + + @Override public void setStopUserOnSwitch(int value) { ActivityManagerService.this.setStopUserOnSwitch(value); } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 30efa3e87fc6..e57fe133eac8 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -439,6 +439,15 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>(); + /** + * Contains users which cannot abort the shutdown process. + * + * <p> For example, we don't abort shutdown for users whose processes have already been stopped + * due to {@link #isEarlyPackageKillEnabledForUserSwitch(int, int)}. + */ + @GuardedBy("mLock") + private final ArraySet<Integer> mDoNotAbortShutdownUserIds = new ArraySet<>(); + private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() { @Override public void onUserCreated(UserInfo user, Object token) { @@ -509,11 +518,11 @@ class UserController implements Handler.Callback { } } - private boolean shouldStopUserOnSwitch() { + private boolean isStopUserOnSwitchEnabled() { synchronized (mLock) { if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) { final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE; - Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value); + Slogf.i(TAG, "isStopUserOnSwitchEnabled(): returning overridden value (%b)", value); return value; } } @@ -521,6 +530,26 @@ class UserController implements Handler.Callback { return property == -1 ? mDelayUserDataLocking : property == 1; } + /** + * Get whether or not the previous user's packages will be killed before the user is + * stopped during a user switch. + * + * <p> The primary use case of this method is for {@link com.android.server.SystemService} + * classes to call this API in their + * {@link com.android.server.SystemService#onUserSwitching} method implementation to prevent + * restarting any of the previous user's processes that will be killed during the user switch. + */ + boolean isEarlyPackageKillEnabledForUserSwitch(int fromUserId, int toUserId) { + // NOTE: The logic in this method could be extended to cover other cases where + // the previous user is also stopped like: guest users, ephemeral users, + // and users with DISALLOW_RUN_IN_BACKGROUND. Currently, this is not done + // because early killing is not enabled for these cases by default. + if (fromUserId == UserHandle.USER_SYSTEM) { + return false; + } + return isStopUserOnSwitchEnabled(); + } + void finishUserSwitch(UserState uss) { // This call holds the AM lock so we post to the handler. mHandler.post(() -> { @@ -1247,6 +1276,7 @@ class UserController implements Handler.Callback { return; } uss.setState(UserState.STATE_SHUTDOWN); + mDoNotAbortShutdownUserIds.remove(userId); } TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]"); @@ -1555,7 +1585,8 @@ class UserController implements Handler.Callback { private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) { if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason); - mInjector.activityManagerForceStopPackage(userId, reason); + mInjector.activityManagerForceStopUserPackages(userId, reason, + /* evenImportantServices= */ true); if (mInjector.getUserManager().isPreCreated(userId)) { // Don't fire intent for precreated. return; @@ -1608,6 +1639,21 @@ class UserController implements Handler.Callback { } } + private void stopPreviousUserPackagesIfEnabled(int fromUserId, int toUserId) { + if (!android.multiuser.Flags.stopPreviousUserApps() + || !isEarlyPackageKillEnabledForUserSwitch(fromUserId, toUserId)) { + return; + } + // Stop the previous user's packages early to reduce resource usage + // during user switching. Only do this when the previous user will + // be stopped regardless. + synchronized (mLock) { + mDoNotAbortShutdownUserIds.add(fromUserId); + } + mInjector.activityManagerForceStopUserPackages(fromUserId, + "early stop user packages", /* evenImportantServices= */ false); + } + void scheduleStartProfiles() { // Parent user transition to RUNNING_UNLOCKING happens on FgThread, so it is busy, there is // a chance the profile will reach RUNNING_LOCKED while parent is still locked, so no @@ -1889,7 +1935,8 @@ class UserController implements Handler.Callback { updateStartedUserArrayLU(); needStart = true; updateUmState = true; - } else if (uss.state == UserState.STATE_SHUTDOWN) { + } else if (uss.state == UserState.STATE_SHUTDOWN + || mDoNotAbortShutdownUserIds.contains(userId)) { Slogf.i(TAG, "User #" + userId + " is shutting down - will start after full shutdown"); mPendingUserStarts.add(new PendingUserStart(userId, userStartMode, @@ -2293,7 +2340,7 @@ class UserController implements Handler.Callback { hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); synchronized (mLock) { // If running in background is disabled or mStopUserOnSwitch mode, stop the user. - if (hasRestriction || shouldStopUserOnSwitch()) { + if (hasRestriction || isStopUserOnSwitchEnabled()) { Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId); stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null); return; @@ -3425,7 +3472,7 @@ class UserController implements Handler.Callback { pw.println(" mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking); - pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); + pw.println(" isStopUserOnSwitchEnabled():" + isStopUserOnSwitchEnabled()); pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mBackgroundUserScheduledStopTimeSecs:" @@ -3522,6 +3569,7 @@ class UserController implements Handler.Callback { Integer.toString(msg.arg1), msg.arg1); mInjector.getSystemServiceManager().onUserSwitching(msg.arg2, msg.arg1); + stopPreviousUserPackagesIfEnabled(msg.arg2, msg.arg1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_SWITCHING, USER_COMPLETED_EVENT_DELAY_MS); @@ -3896,10 +3944,10 @@ class UserController implements Handler.Callback { }.sendNext(); } - void activityManagerForceStopPackage(@UserIdInt int userId, String reason) { + void activityManagerForceStopUserPackages(@UserIdInt int userId, String reason, + boolean evenImportantServices) { synchronized (mService) { - mService.forceStopPackageLocked(null, -1, false, false, true, false, false, false, - userId, reason); + mService.forceStopUserPackagesLocked(userId, reason, evenImportantServices); } }; 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 0fdd57d64d8d..dca14914a572 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 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { } }); } + + @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 8dc560b0e0b5..caa2c1c34ff7 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 @@ class BiometricTestSessionImpl extends ITestSession.Stub { } }); } + + @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) + @Override + public int getSensorId() { + super.getSensorId_enforcePermission(); + return mSensorId; + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 7746276ac505..3161b770dca6 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 @@ public class HdmiCecMessageValidator { 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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3863cf0a04cf..8afbd56728e4 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -330,7 +330,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @UserIdInt @BinderThread private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) { - return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId; + return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId; } /** @@ -343,7 +343,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @UserIdInt private int resolveImeUserIdFromDisplayIdLocked(int displayId) { return mConcurrentMultiUserModeEnabled - ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId; + ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId; } /** @@ -359,7 +359,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); return mUserManagerInternal.getUserAssignedToDisplay(displayId); } - return mCurrentUserId; + return mCurrentImeUserId; } final Context mContext; @@ -370,10 +370,23 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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 @@ -540,7 +553,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable IInputMethodInvoker getCurMethodLocked() { - return getInputMethodBindingController(mCurrentUserId).getCurMethod(); + return getInputMethodBindingController(mCurrentImeUserId).getCurMethod(); } /** @@ -587,7 +600,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. switch (key) { case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: { if (!Flags.imeSwitcherRevamp()) { - if (userId == mCurrentUserId) { + if (userId == mCurrentImeUserId) { mMenuController.updateKeyboardFromSettingsLocked(userId); } } @@ -649,11 +662,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // sender userId can be a real user ID or USER_ALL. final int senderUserId = pendingResult.getSendingUserId(); synchronized (ImfLock.class) { - if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentUserId) { + if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentImeUserId) { // A background user is trying to hide the dialog. Ignore. return; } - final int userId = mCurrentUserId; + final int userId = mCurrentImeUserId; if (Flags.imeSwitcherRevamp()) { final var bindingController = getInputMethodBindingController(userId); mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId); @@ -1187,7 +1200,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mShowOngoingImeSwitcherForPhones = false; - mCurrentUserId = mActivityManagerInternal.getCurrentUserId(); + mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId(); final IntFunction<InputMethodBindingController> bindingControllerFactory = userId -> new InputMethodBindingController(userId, InputMethodManagerService.this); @@ -1282,7 +1295,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @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); @@ -1307,7 +1320,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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); @@ -1407,13 +1420,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } if (!mSystemReady) { mSystemReady = true; - final int currentUserId = mCurrentUserId; + final int currentImeUserId = mCurrentImeUserId; mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); - hideStatusBarIconLocked(currentUserId); - final var bindingController = getInputMethodBindingController(currentUserId); + hideStatusBarIconLocked(currentImeUserId); + final var bindingController = getInputMethodBindingController(currentImeUserId); updateSystemUiLocked(bindingController.getImeWindowVis(), - bindingController.getBackDisposition(), currentUserId); + bindingController.getBackDisposition(), currentImeUserId); mShowOngoingImeSwitcherForPhones = mRes.getBoolean( com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { @@ -1593,7 +1606,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Check if selected IME of current user supports handwriting. - if (userId == mCurrentUserId) { + if (userId == mCurrentImeUserId) { final var bindingController = getInputMethodBindingController(userId); return bindingController.supportsStylusHandwriting() && (!connectionless @@ -2545,7 +2558,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int userId = userData.mUserId; // To minimize app compat risk, ignore background users' request for single-user mode. // TODO(b/357178609): generalize the logic and remove this special rule. - if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) { + if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { return; } if (iconId == 0) { @@ -2577,7 +2590,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void hideStatusBarIconLocked(@UserIdInt int userId) { // To minimize app compat risk, ignore background users' request for single-user mode. // TODO(b/357178609): generalize the logic and remove this special rule. - if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) { + if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { return; } if (mStatusBarManagerInternal != null) { @@ -2777,7 +2790,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) { // To minimize app compat risk, ignore background users' request for single-user mode. // TODO(b/357178609): generalize the logic and remove this special rule. - if (!mConcurrentMultiUserModeEnabled && userId != mCurrentUserId) { + if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { return; } final var userData = getUserData(userId); @@ -2933,7 +2946,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // TODO(b/357663774): Figure out how to better handle this scenario. userData.mSubtypeForKeyboardLayoutMapping = Pair.create(newSubtypeHandle, normalizedSubtype); - if (userId != mCurrentUserId) { + if (userId != mCurrentImeUserId) { return; } mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping( @@ -3703,7 +3716,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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,9 +3763,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // 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. @@ -4170,29 +4183,27 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // 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(); - subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi, - keyboardSubtype.hashCode()); - if (keyboardSubtype.getLocale().equals(locale)) { - break; - } + 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; } } } @@ -4442,7 +4453,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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(); @@ -4631,7 +4642,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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; @@ -4950,7 +4961,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 @@ -5016,7 +5027,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. case MSG_RESET_HANDWRITING: { synchronized (ImfLock.class) { - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final var bindingController = + getInputMethodBindingController(mCurrentImeUserId); if (bindingController.supportsStylusHandwriting() && bindingController.getCurMethod() != null && hasSupportedStylusLocked()) { @@ -5100,7 +5112,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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; @@ -6059,7 +6071,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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:"); @@ -6091,7 +6103,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }; 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=" @@ -6184,7 +6196,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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) { @@ -6415,7 +6427,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final int[] userIds; synchronized (ImfLock.class) { - userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentUserId, + userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentImeUserId, shellCommand.getErrPrintWriter()); } try (PrintWriter pr = shellCommand.getOutPrintWriter()) { @@ -6461,7 +6473,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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; @@ -6556,7 +6568,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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; @@ -6576,6 +6588,28 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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; } @@ -6597,7 +6631,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 4dbbfa2be334..c77b76864176 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -512,6 +512,9 @@ final class InputMethodSubtypeSwitchingController { /** * Gets the next input method and subtype from the given ones. * + * <p>If the given input method and subtype are not found, this returns the most recent + * input method and subtype.</p> + * * @param imi the input method to find the next value from. * @param subtype the input method subtype to find the next value from, if any. * @param onlyCurrentIme whether to consider only subtypes of the current input method. @@ -523,17 +526,20 @@ final class InputMethodSubtypeSwitchingController { public ImeSubtypeListItem next(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype, boolean onlyCurrentIme, boolean useRecency, boolean forward) { - final int size = mItems.size(); - if (size <= 1) { + if (mItems.isEmpty()) { return null; } final int index = getIndex(imi, subtype, useRecency); if (index < 0) { - return null; + Slog.w(TAG, "Trying to switch away from input method: " + imi + + " and subtype " + subtype + " which are not in the list," + + " falling back to most recent item in list."); + return mItems.get(mRecencyMap[0]); } final int incrementSign = (forward ? 1 : -1); + final int size = mItems.size(); for (int i = 1; i < size; i++) { final int nextIndex = (index + i * incrementSign + size) % size; final int mappedIndex = useRecency ? mRecencyMap[nextIndex] : nextIndex; @@ -554,7 +560,7 @@ final class InputMethodSubtypeSwitchingController { */ public boolean setMostRecent(@NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { - if (mItems.size() <= 1) { + if (mItems.isEmpty()) { return false; } @@ -849,6 +855,9 @@ final class InputMethodSubtypeSwitchingController { /** * Gets the next input method and subtype, starting from the given ones, in the given direction. * + * <p>If the given input method and subtype are not found, this returns the most recent + * input method and subtype.</p> + * * @param onlyCurrentIme whether to consider only subtypes of the current input method. * @param imi the input method to find the next value from. * @param subtype the input method subtype to find the next value from, if any. @@ -867,6 +876,9 @@ final class InputMethodSubtypeSwitchingController { * Gets the next input method and subtype suitable for hardware keyboards, starting from the * given ones, in the given direction. * + * <p>If the given input method and subtype are not found, this returns the most recent + * input method and subtype.</p> + * * @param onlyCurrentIme whether to consider only subtypes of the current input method. * @param imi the input method to find the next value from. * @param subtype the input method subtype to find the next value from, if any. diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 17f6561cb757..a6f4c0e597d1 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -530,6 +530,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private boolean mUseDifferentDelaysForBackgroundChain; + /** + * Core uids and apps without the internet permission will not have any firewall rules applied + * to them. + */ + private boolean mNeverApplyRulesToCoreUids; + // See main javadoc for instructions on how to use these locks. final Object mUidRulesFirstLock = new Object(); final Object mNetworkPoliciesSecondLock = new Object(); @@ -760,7 +766,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** List of apps indexed by uid and whether they have the internet permission */ @GuardedBy("mUidRulesFirstLock") - private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray(); + @VisibleForTesting + final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray(); /** * Map of uid -> UidStateCallbackInfo objects holding the data received from @@ -1038,6 +1045,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); mUseDifferentDelaysForBackgroundChain = Flags.useDifferentDelaysForBackgroundChain(); + mNeverApplyRulesToCoreUids = Flags.neverApplyRulesToCoreUids(); synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { @@ -4088,6 +4096,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { + mUseMeteredFirewallChains); fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": " + mUseDifferentDelaysForBackgroundChain); + fout.println(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS + ": " + + mNeverApplyRulesToCoreUids); fout.println(); fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode); @@ -4878,6 +4888,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { int[] idleUids = mUsageStats.getIdleUidsForUser(user.id); for (int uid : idleUids) { if (!mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid), false)) { + if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) { + // This check is needed to keep mUidFirewallStandbyRules free of any + // such uids. Doing this keeps it in sync with the actual rules applied + // in the underlying connectivity stack. + continue; + } // quick check: if this uid doesn't have INTERNET permission, it // doesn't have network access anyway, so it is a waste to mess // with it here. @@ -5180,6 +5196,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") private boolean isUidValidForAllowlistRulesUL(int uid) { + return isUidValidForRulesUL(uid); + } + + @GuardedBy("mUidRulesFirstLock") + private boolean isUidValidForRulesUL(int uid) { return UserHandle.isApp(uid) && hasInternetPermissionUL(uid); } @@ -6194,41 +6215,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void addSdkSandboxUidsIfNeeded(SparseIntArray uidRules) { - final int size = uidRules.size(); - final SparseIntArray sdkSandboxUids = new SparseIntArray(); - for (int index = 0; index < size; index++) { - final int uid = uidRules.keyAt(index); - final int rule = uidRules.valueAt(index); - if (Process.isApplicationUid(uid)) { - sdkSandboxUids.put(Process.toSdkSandboxUid(uid), rule); - } - } - - for (int index = 0; index < sdkSandboxUids.size(); index++) { - final int uid = sdkSandboxUids.keyAt(index); - final int rule = sdkSandboxUids.valueAt(index); - uidRules.put(uid, rule); - } - } - /** * Set uid rules on a particular firewall chain. This is going to synchronize the rules given * here to netd. It will clean up dead rules and make sure the target chain only contains rules * specified here. */ + @GuardedBy("mUidRulesFirstLock") private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) { - addSdkSandboxUidsIfNeeded(uidRules); try { int size = uidRules.size(); - int[] uids = new int[size]; - int[] rules = new int[size]; + final IntArray uids = new IntArray(size); + final IntArray rules = new IntArray(size); for(int index = size - 1; index >= 0; --index) { - uids[index] = uidRules.keyAt(index); - rules[index] = uidRules.valueAt(index); + final int uid = uidRules.keyAt(index); + if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) { + continue; + } + uids.add(uid); + rules.add(uidRules.valueAt(index)); + if (Process.isApplicationUid(uid)) { + uids.add(Process.toSdkSandboxUid(uid)); + rules.add(uidRules.valueAt(index)); + } } - mNetworkManager.setFirewallUidRules(chain, uids, rules); - mLogger.firewallRulesChanged(chain, uids, rules); + final int[] uidArray = uids.toArray(); + final int[] ruleArray = rules.toArray(); + mNetworkManager.setFirewallUidRules(chain, uidArray, ruleArray); + mLogger.firewallRulesChanged(chain, uidArray, ruleArray); } catch (IllegalStateException e) { Log.wtf(TAG, "problem setting firewall uid rules", e); } catch (RemoteException e) { @@ -6241,6 +6254,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ @GuardedBy("mUidRulesFirstLock") private void setUidFirewallRuleUL(int chain, int uid, int rule) { + if (mNeverApplyRulesToCoreUids && !isUidValidForRulesUL(uid)) { + return; + } if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule); @@ -6249,8 +6265,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (chain == FIREWALL_CHAIN_STANDBY) { mUidFirewallStandbyRules.put(uid, rule); } - // Note that we do not need keep a separate cache of uid rules for chains that we do - // not call #setUidFirewallRulesUL for. try { mNetworkManager.setFirewallUidRule(chain, uid, rule); @@ -6295,6 +6309,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * Resets all firewall rules associated with an UID. */ private void resetUidFirewallRules(int uid) { + // Resetting rules for uids with isUidValidForRulesUL = false should be OK as no rules + // should be previously set and the downstream code will skip no-op changes. try { mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_DOZABLE, uid, FIREWALL_RULE_DEFAULT); diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index 586baf022897..7f04e665567e 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -27,3 +27,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "never_apply_rules_to_core_uids" + namespace: "backstage_power" + description: "Removes all rule bookkeeping and evaluation logic for core uids and uids without the internet permission" + bug: "356956588" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index a44e55344fe3..66e61c076030 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -16,9 +16,6 @@ package com.android.server.notification; -import static android.service.notification.Condition.STATE_TRUE; -import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; - import android.app.INotificationManager; import android.app.NotificationManager; import android.content.ComponentName; @@ -322,20 +319,7 @@ public class ConditionProviders extends ManagedServices { final Condition c = conditions[i]; final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); r.info = info; - if (android.app.Flags.modesUi()) { - // 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 == ORIGIN_USER_IN_SYSTEMUI) { - if (r.condition.state == STATE_TRUE && c.state == STATE_TRUE) { - r.condition = c; - } - } else { - r.condition = c; - } - } else { - r.condition = c; - } + r.condition = c; } } final int N = conditions.length; diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index b4459cb2fe92..981891669e7c 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -1519,7 +1519,14 @@ public final class NotificationAttentionHelper { @Override public void setLastNotificationUpdateTimeMs(NotificationRecord record, long timestampMillis) { - super.setLastNotificationUpdateTimeMs(record, timestampMillis); + if (Flags.politeNotificationsAttnUpdate()) { + // Set last update per package/channel only for exempt notifications + if (isAvalancheExempted(record)) { + super.setLastNotificationUpdateTimeMs(record, timestampMillis); + } + } else { + super.setLastNotificationUpdateTimeMs(record, timestampMillis); + } mLastNotificationTimestamp = timestampMillis; mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 45b8da5fc8ea..c7c984b40267 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 @@ public class NotificationManagerService extends SystemService { }; 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()); } } }); @@ -8667,8 +8668,8 @@ public class NotificationManagerService extends SystemService { 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) { @@ -9390,8 +9391,8 @@ public class NotificationManagerService extends SystemService { 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()); } } @@ -10372,13 +10373,45 @@ public class NotificationManagerService extends SystemService { 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") @@ -10539,18 +10572,19 @@ public class NotificationManagerService extends SystemService { // 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); } @@ -10558,12 +10592,13 @@ public class NotificationManagerService extends SystemService { 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 bd009010a313..1392003a13e7 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 @@ public final class NotificationRecord { 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/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 268d835e704c..d495ef5ce108 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -82,7 +82,7 @@ public class ZenModeConditions implements ConditionProviders.Callback { for (ZenRule automaticRule : config.automaticRules.values()) { if (automaticRule.component != null) { evaluateRule(automaticRule, current, trigger, processSubscriptions, false); - updateSnoozing(automaticRule); + automaticRule.reconsiderConditionOverride(); } } @@ -187,13 +187,4 @@ public class ZenModeConditions implements ConditionProviders.Callback { + rule.conditionId); } } - - private boolean updateSnoozing(ZenRule rule) { - if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) { - rule.snoozing = false; - if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId); - return true; - } - return false; - } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 8c280edf03c0..db48835a9b82 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -37,6 +37,8 @@ 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 android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.Preconditions.checkArgument; @@ -643,10 +645,10 @@ public class ZenModeHelper { if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) { rule.zenMode = zenMode; } - rule.snoozing = false; rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE); + rule.resetConditionOverride(); setConfigLocked(newConfig, /* triggeringComponent= */ null, ORIGIN_APP, "applyGlobalZenModeAsImplicitZenRule", callingUid); @@ -867,8 +869,8 @@ public class ZenModeHelper { ZenRule deletedRule = ruleToRemove.copy(); deletedRule.deletionInstant = Instant.now(mClock); // If the rule is restored it shouldn't be active (or snoozed). - deletedRule.snoozing = false; deletedRule.condition = null; + deletedRule.resetConditionOverride(); // Overwrites a previously-deleted rule with the same conditionId, but that's okay. config.deletedRules.put(deletedKey, deletedRule); } @@ -885,7 +887,12 @@ public class ZenModeHelper { if (rule == null || !canManageAutomaticZenRule(rule)) { return Condition.STATE_UNKNOWN; } - return rule.condition != null ? rule.condition.state : STATE_FALSE; + if (Flags.modesApi() && Flags.modesUi()) { + return rule.isAutomaticActive() ? STATE_TRUE : STATE_FALSE; + } else { + // Buggy, does not consider snoozing! + return rule.condition != null ? rule.condition.state : STATE_FALSE; + } } } @@ -943,12 +950,40 @@ public class ZenModeHelper { } for (ZenRule rule : rules) { - rule.condition = condition; - updateSnoozing(rule); + applyConditionAndReconsiderOverride(rule, condition, origin); setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid); } } + private static void applyConditionAndReconsiderOverride(ZenRule rule, Condition condition, + int origin) { + if (Flags.modesApi() && Flags.modesUi()) { + if (origin == ORIGIN_USER_IN_SYSTEMUI && condition != null + && condition.source == SOURCE_USER_ACTION) { + // Apply as override, instead of actual condition. + if (condition.state == STATE_TRUE) { + // Manually turn on a rule -> Apply override. + rule.setConditionOverride(OVERRIDE_ACTIVATE); + } else if (condition.state == STATE_FALSE) { + // Manually turn off a rule. If the rule was manually activated before, reset + // override -- but only if this will not result in the rule turning on + // immediately because of a previously snoozed condition! In that case, apply + // deactivate-override. + rule.resetConditionOverride(); + if (rule.isAutomaticActive()) { + rule.setConditionOverride(OVERRIDE_DEACTIVATE); + } + } + } else { + rule.condition = condition; + rule.reconsiderConditionOverride(); + } + } else { + rule.condition = condition; + rule.reconsiderConditionOverride(); + } + } + private static List<ZenRule> findMatchingRules(ZenModeConfig config, Uri id, Condition condition) { List<ZenRule> matchingRules = new ArrayList<>(); @@ -971,15 +1006,6 @@ public class ZenModeHelper { return true; } - private boolean updateSnoozing(ZenRule rule) { - if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) { - rule.snoozing = false; - if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId); - return true; - } - return false; - } - public int getCurrentInstanceCount(ComponentName cn) { if (cn == null) { return 0; @@ -1181,7 +1207,7 @@ public class ZenModeHelper { if (rule.enabled != azr.isEnabled()) { rule.enabled = azr.isEnabled(); - rule.snoozing = false; + rule.resetConditionOverride(); modified = true; } if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) { @@ -1271,7 +1297,7 @@ public class ZenModeHelper { return modified; } else { if (rule.enabled != azr.isEnabled()) { - rule.snoozing = false; + rule.resetConditionOverride(); } rule.name = azr.getName(); rule.condition = null; @@ -1573,18 +1599,16 @@ public class ZenModeHelper { // For API calls (different origin) keep old behavior of snoozing all rules. for (ZenRule automaticRule : newConfig.automaticRules.values()) { if (automaticRule.isAutomaticActive()) { - automaticRule.snoozing = true; + automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE); } } } } else { if (zenMode == Global.ZEN_MODE_OFF) { newConfig.manualRule = null; - // 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()) { if (automaticRule.isAutomaticActive()) { - automaticRule.snoozing = true; + automaticRule.setConditionOverride(OVERRIDE_DEACTIVATE); } } @@ -1626,13 +1650,11 @@ public class ZenModeHelper { void dump(ProtoOutputStream proto) { proto.write(ZenModeProto.ZEN_MODE, mZenMode); synchronized (mConfigLock) { - if (mConfig.manualRule != null) { + if (mConfig.isManualActive()) { mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } for (ZenRule rule : mConfig.automaticRules.values()) { - if (rule.enabled && rule.condition != null - && rule.condition.state == STATE_TRUE - && !rule.snoozing) { + if (rule.isAutomaticActive()) { rule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS); } } @@ -1695,8 +1717,8 @@ public class ZenModeHelper { for (ZenRule automaticRule : config.automaticRules.values()) { if (forRestore) { // don't restore transient state from restored automatic rules - automaticRule.snoozing = false; automaticRule.condition = null; + automaticRule.resetConditionOverride(); automaticRule.creationTime = time; } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 46585a50ea36..6303ecd53dbb 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -80,6 +80,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.KeepForWeakReference; import com.android.internal.content.PackageMonitor; import com.android.internal.content.om.OverlayConfig; @@ -1180,6 +1181,7 @@ public final class OverlayManagerService extends SystemService { // intent, querying the PackageManagerService for the actual current // state may lead to contradictions within OMS. Better then to lag // behind until all pending intents have been processed. + @GuardedBy("itself") private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>(); private final ArraySet<Integer> mInitializedUsers = new ArraySet<>(); @@ -1207,10 +1209,12 @@ public final class OverlayManagerService extends SystemService { } final ArrayMap<String, PackageState> userPackages = new ArrayMap<>(); - for (int i = 0, n = mCache.size(); i < n; i++) { - final PackageStateUsers pkg = mCache.valueAt(i); - if (pkg.mInstalledUsers.contains(userId)) { - userPackages.put(mCache.keyAt(i), pkg.mPackageState); + synchronized (mCache) { + for (int i = 0, n = mCache.size(); i < n; i++) { + final PackageStateUsers pkg = mCache.valueAt(i); + if (pkg.mInstalledUsers.contains(userId)) { + userPackages.put(mCache.keyAt(i), pkg.mPackageState); + } } } return userPackages; @@ -1220,7 +1224,11 @@ public final class OverlayManagerService extends SystemService { @Nullable public PackageState getPackageStateForUser(@NonNull final String packageName, final int userId) { - final PackageStateUsers pkg = mCache.get(packageName); + final PackageStateUsers pkg; + + synchronized (mCache) { + pkg = mCache.get(packageName); + } if (pkg != null && pkg.mInstalledUsers.contains(userId)) { return pkg.mPackageState; } @@ -1251,12 +1259,15 @@ public final class OverlayManagerService extends SystemService { @NonNull private PackageState addPackageUser(@NonNull final PackageState pkg, final int user) { - PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName()); - if (pkgUsers == null) { - pkgUsers = new PackageStateUsers(pkg); - mCache.put(pkg.getPackageName(), pkgUsers); - } else { - pkgUsers.mPackageState = pkg; + PackageStateUsers pkgUsers; + synchronized (mCache) { + pkgUsers = mCache.get(pkg.getPackageName()); + if (pkgUsers == null) { + pkgUsers = new PackageStateUsers(pkg); + mCache.put(pkg.getPackageName(), pkgUsers); + } else { + pkgUsers.mPackageState = pkg; + } } pkgUsers.mInstalledUsers.add(user); return pkgUsers.mPackageState; @@ -1265,18 +1276,24 @@ public final class OverlayManagerService extends SystemService { @NonNull private void removePackageUser(@NonNull final String packageName, final int user) { - final PackageStateUsers pkgUsers = mCache.get(packageName); - if (pkgUsers == null) { - return; + // synchronize should include the call to the other removePackageUser() method so that + // the access and modification happen under the same lock. + synchronized (mCache) { + final PackageStateUsers pkgUsers = mCache.get(packageName); + if (pkgUsers == null) { + return; + } + removePackageUser(pkgUsers, user); } - removePackageUser(pkgUsers, user); } @NonNull private void removePackageUser(@NonNull final PackageStateUsers pkg, final int user) { pkg.mInstalledUsers.remove(user); if (pkg.mInstalledUsers.isEmpty()) { - mCache.remove(pkg.mPackageState.getPackageName()); + synchronized (mCache) { + mCache.remove(pkg.mPackageState.getPackageName()); + } } } @@ -1386,8 +1403,10 @@ public final class OverlayManagerService extends SystemService { public void forgetAllPackageInfos(final int userId) { // Iterate in reverse order since removing the package in all users will remove the // package from the cache. - for (int i = mCache.size() - 1; i >= 0; i--) { - removePackageUser(mCache.valueAt(i), userId); + synchronized (mCache) { + for (int i = mCache.size() - 1; i >= 0; i--) { + removePackageUser(mCache.valueAt(i), userId); + } } } @@ -1405,22 +1424,23 @@ public final class OverlayManagerService extends SystemService { public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) { pw.println("AndroidPackage cache"); + synchronized (mCache) { + if (!dumpState.isVerbose()) { + pw.println(TAB1 + mCache.size() + " package(s)"); + return; + } - if (!dumpState.isVerbose()) { - pw.println(TAB1 + mCache.size() + " package(s)"); - return; - } - - if (mCache.size() == 0) { - pw.println(TAB1 + "<empty>"); - return; - } + if (mCache.size() == 0) { + pw.println(TAB1 + "<empty>"); + return; + } - for (int i = 0, n = mCache.size(); i < n; i++) { - final String packageName = mCache.keyAt(i); - final PackageStateUsers pkg = mCache.valueAt(i); - pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users="); - pw.println(TextUtils.join(", ", pkg.mInstalledUsers)); + for (int i = 0, n = mCache.size(); i < n; i++) { + final String packageName = mCache.keyAt(i); + final PackageStateUsers pkg = mCache.valueAt(i); + pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users="); + pw.println(TextUtils.join(", ", pkg.mInstalledUsers)); + } } } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index a0d5ea875abf..98e3e24c36b9 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 @@ final class InstallPackageHelper { 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/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 9f10e0166120..d374142d3912 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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal SCANNED_AS_STOPPED_SYSTEM_APP, PENDING_RESTORE, DEBUGGABLE, + IS_LEAVING_SHARED_USER, }) public @interface Flags { } @@ -107,6 +108,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal 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 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } /** + * @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 7afc35819aa7..26da84f99f5e 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 @@ final class RemovePackageHelper { // 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 95561f5fe0e3..61fddbae0d22 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 @@ final class ScanPackageUtils { + " 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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 829ee27287c2..57d7d79b2392 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2105,6 +2105,10 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setUserAdmin(@UserIdInt int userId) { checkManageUserAndAcrossUsersFullPermission("set user admin"); + if (Flags.unicornModeRefactoringForHsumReadOnly()) { + checkAdminStatusChangeAllowed(userId); + } + mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_GRANT_ADMIN); UserData user; synchronized (mPackagesLock) { @@ -2133,6 +2137,10 @@ public class UserManagerService extends IUserManager.Stub { @Override public void revokeUserAdmin(@UserIdInt int userId) { checkManageUserAndAcrossUsersFullPermission("revoke admin privileges"); + if (Flags.unicornModeRefactoringForHsumReadOnly()) { + checkAdminStatusChangeAllowed(userId); + } + mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_REVOKE_ADMIN); UserData user; synchronized (mPackagesLock) { @@ -4065,6 +4073,26 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Checks if changing the admin status of a target user is restricted + * due to the DISALLOW_GRANT_ADMIN restriction. If either the calling + * user or the target user has this restriction, a SecurityException + * is thrown. + * + * @param targetUser The user ID of the user whose admin status is being + * considered for change. + * @throws SecurityException if the admin status change is restricted due + * to the DISALLOW_GRANT_ADMIN restriction. + */ + private void checkAdminStatusChangeAllowed(int targetUser) { + if (hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, UserHandle.getCallingUserId()) + || hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, targetUser)) { + throw new SecurityException( + "Admin status change is restricted. The DISALLOW_GRANT_ADMIN " + + "restriction is applied either on the current or the target user."); + } + } + @GuardedBy({"mPackagesLock"}) private void writeBitmapLP(UserInfo info, Bitmap bitmap) { try { @@ -5443,6 +5471,13 @@ public class UserManagerService extends IUserManager.Stub { enforceUserRestriction(restriction, UserHandle.getCallingUserId(), "Cannot add user"); + if (Flags.unicornModeRefactoringForHsumReadOnly()) { + if ((flags & UserInfo.FLAG_ADMIN) != 0) { + enforceUserRestriction(UserManager.DISALLOW_GRANT_ADMIN, + UserHandle.getCallingUserId(), "Cannot create ADMIN user"); + } + } + return createUserInternalUnchecked(name, userType, flags, parentId, /* preCreate= */ false, disallowedPackages, /* token= */ null); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 58761886ecb9..bbc17c83cfac 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 @@ public interface PackageState { * @hide */ boolean isScannedAsStoppedSystemApp(); + + /** + * see AndroidPackage#isLeavingSharedUser() + * @hide + */ + boolean isLeavingSharedUser(); } diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java index 39337594ff64..a74c4e07c9ed 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.VibrationEffect; 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 @@ import java.util.Locale; 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 @@ final class VibrationScaler { 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 @@ final class VibrationScaler { } /** + * 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 @@ final class VibrationScaler { @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 @@ final class VibrationScaler { 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 @@ final class VibrationScaler { @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 @@ final class VibrationScaler { /** 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 @@ final class VibrationScaler { 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 f2f5eda7c05a..0d6778c18759 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,7 +16,6 @@ package com.android.server.vibrator; -import static android.os.VibrationAttributes.CATEGORY_KEYBOARD; import static android.os.VibrationAttributes.USAGE_ACCESSIBILITY; import static android.os.VibrationAttributes.USAGE_ALARM; import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; @@ -191,8 +190,6 @@ final class VibrationSettings { @GuardedBy("mLock") private boolean mVibrateOn; @GuardedBy("mLock") - private boolean mKeyboardVibrationOn; - @GuardedBy("mLock") private int mRingerMode; @GuardedBy("mLock") private boolean mOnWirelessCharger; @@ -532,14 +529,6 @@ final class VibrationSettings { return false; } - if (mVibrationConfig.isKeyboardVibrationSettingsSupported()) { - int category = callerInfo.attrs.getCategory(); - if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) { - // Keyboard touch has a different user setting. - return mKeyboardVibrationOn; - } - } - // Apply individual user setting based on usage. return getCurrentIntensity(usage) != Vibrator.VIBRATION_INTENSITY_OFF; } @@ -556,10 +545,11 @@ final class VibrationSettings { mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0, userHandle) > 0; mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1, userHandle) > 0; - mKeyboardVibrationOn = loadSystemSetting( - Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0; - int keyboardIntensity = getDefaultIntensity(USAGE_IME_FEEDBACK); + boolean isKeyboardVibrationOn = loadSystemSetting( + Settings.System.KEYBOARD_VIBRATION_ENABLED, 1, userHandle) > 0; + int keyboardIntensity = toIntensity(isKeyboardVibrationOn, + getDefaultIntensity(USAGE_IME_FEEDBACK)); int alarmIntensity = toIntensity( loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1, userHandle), getDefaultIntensity(USAGE_ALARM)); @@ -654,7 +644,6 @@ final class VibrationSettings { return "VibrationSettings{" + "mVibratorConfig=" + mVibrationConfig + ", mVibrateOn=" + mVibrateOn - + ", mKeyboardVibrationOn=" + mKeyboardVibrationOn + ", mVibrateInputDevices=" + mVibrateInputDevices + ", mBatterySaverMode=" + mBatterySaverMode + ", mRingerMode=" + ringerModeToString(mRingerMode) @@ -671,7 +660,6 @@ final class VibrationSettings { pw.println("VibrationSettings:"); pw.increaseIndent(); pw.println("vibrateOn = " + mVibrateOn); - pw.println("keyboardVibrationOn = " + mKeyboardVibrationOn); pw.println("vibrateInputDevices = " + mVibrateInputDevices); pw.println("batterySaverMode = " + mBatterySaverMode); pw.println("ringerMode = " + ringerModeToString(mRingerMode)); @@ -698,8 +686,6 @@ final class VibrationSettings { void dump(ProtoOutputStream proto) { synchronized (mLock) { proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn); - proto.write(VibratorManagerServiceDumpProto.KEYBOARD_VIBRATION_ON, - mKeyboardVibrationOn); proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode); proto.write(VibratorManagerServiceDumpProto.ALARM_INTENSITY, getCurrentIntensity(USAGE_ALARM)); @@ -774,6 +760,11 @@ final class VibrationSettings { return value; } + @VibrationIntensity + private int toIntensity(boolean enabled, @VibrationIntensity int defaultValue) { + return enabled ? defaultValue : Vibrator.VIBRATION_INTENSITY_OFF; + } + private boolean loadBooleanSetting(String settingKey, int userHandle) { return loadSystemSetting(settingKey, 0, userHandle) != 0; } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 76872cf1274c..f2ad5b95fe5e 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.VibrationEffect; 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 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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); @@ -1698,7 +1700,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IBinder.DeathRecipient { public final ExternalVibration externalVibration; - public ExternalVibrationScale scale = new ExternalVibrationScale(); + public final ExternalVibrationScale scale = new ExternalVibrationScale(); private Vibration.Status mStatus; @@ -1712,8 +1714,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { 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); } @@ -2047,7 +2059,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // 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; @@ -2146,7 +2158,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING), /* continueExternalControl= */ false); // Mute the request, vibration will be ignored. - vibHolder.scale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE; + vibHolder.muteScale(); } return vibHolder.scale; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a22db97f449c..5c096ecbba04 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2524,8 +2524,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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) diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index b4c75572d68f..fe5b142289fc 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -761,7 +761,7 @@ class BackNavigationController { if (isMonitorForRemote()) { mObserver.sendResult(null /* result */); } - if (isMonitorAnimationOrTransition()) { + if (isMonitorAnimationOrTransition() && canCancelAnimations()) { clearBackAnimations(true /* cancel */); } cancelPendingAnimation(); @@ -1935,8 +1935,7 @@ class BackNavigationController { for (int i = penActivities.length - 1; i >= 0; --i) { ActivityRecord resetActivity = penActivities[i]; if (transition.isInTransition(resetActivity)) { - resetActivity.mTransitionController.setReady( - resetActivity.getDisplayContent(), true); + transition.setReady(resetActivity.getDisplayContent(), true); return true; } } @@ -1991,18 +1990,12 @@ class BackNavigationController { activity.makeVisibleIfNeeded(null /* starting */, true /* notifyToClient */); } } - boolean needTransition = false; - final DisplayContent dc = affects.get(0).getDisplayContent(); - for (int i = affects.size() - 1; i >= 0; --i) { - final ActivityRecord activity = affects.get(i); - needTransition |= tc.isCollecting(activity); - } if (prepareOpen != null) { - if (needTransition) { + if (prepareOpen.hasChanges()) { tc.requestStartTransition(prepareOpen, null /*startTask */, null /* remoteTransition */, null /* displayChange */); - tc.setReady(dc); + prepareOpen.setReady(affects.get(0), true); return prepareOpen; } else { prepareOpen.abort(); @@ -2053,11 +2046,22 @@ class BackNavigationController { } } + /** If the open transition is playing, wait for transition to clear the animation */ + private boolean canCancelAnimations() { + if (!Flags.migratePredictiveBackTransition()) { + return true; + } + return mAnimationHandler.mOpenAnimAdaptor == null + || mAnimationHandler.mOpenAnimAdaptor.mPreparedOpenTransition == null; + } + void startAnimation() { if (!mBackAnimationInProgress) { // gesture is already finished, do not start animation if (mPendingAnimation != null) { - clearBackAnimations(true /* cancel */); + if (canCancelAnimations()) { + clearBackAnimations(true /* cancel */); + } mPendingAnimation = null; } return; diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 7a95c2d6d934..2f0ee171b5ba 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 @@ class DisplayWindowSettings { @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; } + if (windowingModeFromDisplaySettings != WindowConfiguration.WINDOWING_MODE_UNDEFINED) { + return windowingModeFromDisplaySettings; + } // 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; + 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 windowingMode; + return currentWindowingMode; } @WindowConfiguration.WindowingMode diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 7a0fd3e34fdd..5d8a96c530ef 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 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; @@ -50,6 +51,7 @@ import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING; import android.annotation.Nullable; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.os.Trace; import android.util.Slog; import android.util.SparseArray; @@ -77,6 +79,8 @@ class KeyguardController { 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 +236,7 @@ class KeyguardController { dc.mWallpaperController.adjustWallpaperWindows(); dc.executeAppTransition(); } + scheduleGoingAwayTimeout(displayId); } // Update the sleep token first such that ensureActivitiesVisible has correct sleep token @@ -286,6 +291,8 @@ class KeyguardController { mRootWindowContainer.ensureActivitiesVisible(); mRootWindowContainer.addStartingWindowsForVisibleActivities(); mWindowManager.executeAppTransition(); + + scheduleGoingAwayTimeout(displayId); } finally { mService.continueWindowLayout(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -417,31 +424,42 @@ class KeyguardController { 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()); + + 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(occluded); + 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 +469,8 @@ class KeyguardController { } } updateKeyguardSleepToken(displayId); - if (performTransition && executeTransition) { - mWindowManager.executeAppTransition(); + if (executeTransition) { + dc.executeAppTransition(); } } finally { mService.continueWindowLayout(); @@ -590,6 +608,35 @@ class KeyguardController { } } + /** + * 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( + SystemClock.uptimeMillis() - GOING_AWAY_TIMEOUT_MS); + } + }; + + 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 +756,7 @@ class KeyguardController { if (!lastKeyguardGoingAway && mKeyguardGoingAway) { writeEventLog("dismissIfInsecure"); controller.handleDismissInsecureKeyguard(display); + controller.scheduleGoingAwayTimeout(mDisplayId); hasChange = true; } else if (lastOccluded != mOccluded) { controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 59e0d68589fe..0e33734fc7a5 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -150,6 +150,9 @@ final class LetterboxUiController { return; } + pw.println(prefix + "isTransparentPolicyRunning=" + + mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()); + boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed(); pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed); if (!areBoundsLetterboxed) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d3df5fdcc447..12e91adef9c5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3507,7 +3507,7 @@ class Task extends TaskFragment { | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS); if ((info.startingWindowTypeParameter & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) { - final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION); + final WindowState topMainWin = getTopFullscreenMainWindow(); if (topMainWin != null) { info.mainWindowLayoutParams = topMainWin.getAttrs(); info.requestedVisibleTypes = topMainWin.getRequestedVisibleTypes(); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index eaf3012a3b11..dba1c364b89b 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 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // 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 5698750170f4..486a61b7aef6 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 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mToken; } - void addFlag(int flag) { - mFlags |= flag; + void addFlag(@TransitionFlags int flags) { + mFlags |= flags; + } + + void removeFlag(@TransitionFlags int flags) { + mFlags &= ~flags; } void calcParallelCollectType(WindowContainerTransaction wct) { @@ -3350,6 +3354,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { 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/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp index 2edf129929cc..cf9611468fab 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 @@ void AnrTimerService::scrubExpiredLocked() { } // 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 @@ void AnrTimerService::expire(timer_id_t timerId) { // Deliver the notification outside of the lock. if (expired) { if (!notifier_(timerId, pid, uid, elapsed, notifierCookie_, notifierObject_)) { - AutoMutex _l(lock_); // Notification failed, which means the listener will never call accept() or // discard(). Do not reinsert the timer. discard(timerId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 669a999c921e..a08af72586ee 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 @@ final class DevicePolicyEngine { 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 @@ final class DevicePolicyEngine { } } - private void readLocalPoliciesInner(TypedXmlPullParser parser) - throws XmlPullParserException, IOException { - int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID); - PolicyKey policyKey = null; - PolicyState<?> policyState = null; - int outerDepth = parser.getDepth(); - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - String tag = parser.getName(); - switch (tag) { - case TAG_POLICY_KEY_ENTRY: - policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); - break; - case TAG_POLICY_STATE_ENTRY: - policyState = PolicyState.readFromXml(parser); - break; - default: - Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag); - } - } - - if (policyKey != null && policyState != null) { - if (!mLocalPolicies.contains(userId)) { - mLocalPolicies.put(userId, new HashMap<>()); - } - mLocalPolicies.get(userId).put(policyKey, policyState); - } else { - Slogf.wtf(TAG, "Error parsing local policy, policyKey is " - + (policyKey == null ? "null" : policyKey) + ", and policyState is " - + (policyState == null ? "null" : policyState) + "."); - } - } - - private void readGlobalPoliciesInner(TypedXmlPullParser parser) + private static void readPoliciesInner( + TypedXmlPullParser parser, Map<PolicyKey, PolicyState<?>> policyStateMap) throws IOException, XmlPullParserException { PolicyKey policyKey = null; + PolicyDefinition<?> policyDefinition = null; PolicyState<?> policyState = null; int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { String tag = parser.getName(); switch (tag) { case TAG_POLICY_KEY_ENTRY: - policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); + if (Flags.dontReadPolicyDefinition()) { + policyDefinition = PolicyDefinition.readFromXml(parser); + if (policyDefinition != null) { + policyKey = policyDefinition.getPolicyKey(); + } + } else { + policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); + } break; case TAG_POLICY_STATE_ENTRY: - policyState = PolicyState.readFromXml(parser); + if (Flags.dontReadPolicyDefinition() && policyDefinition == null) { + Slogf.w(TAG, "Skipping policy state - unknown policy definition"); + } else { + policyState = PolicyState.readFromXml(policyDefinition, parser); + } break; default: - Slogf.wtf(TAG, "Unknown tag for local policy entry" + tag); + Slogf.wtf(TAG, "Unknown tag for policy entry" + tag); } } - if (policyKey != null && policyState != null) { - mGlobalPolicies.put(policyKey, policyState); - } else { - Slogf.wtf(TAG, "Error parsing global policy, policyKey is " - + (policyKey == null ? "null" : policyKey) + ", and policyState is " - + (policyState == null ? "null" : policyState) + "."); + if (policyKey == null || policyState == null) { + Slogf.wtf(TAG, "Error parsing policy, policyKey is %s, and policyState is %s.", + policyKey, policyState); + return; } + + policyStateMap.put(policyKey, policyState); } private void readEnforcingAdminsInner(TypedXmlPullParser parser) diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 8e3248eaa6bf..19a942cd2eed 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -129,9 +129,8 @@ final class PolicyDefinition<V> { */ static PolicyDefinition<Integer> PERMISSION_GRANT( @NonNull String packageName, @NonNull String permissionName) { - if (packageName == null || permissionName == null) { - return GENERIC_PERMISSION_GRANT; - } + Objects.requireNonNull(packageName, "packageName must not be null"); + Objects.requireNonNull(permissionName, "permissionName must not be null"); return GENERIC_PERMISSION_GRANT.createPolicyDefinition( new PackagePermissionPolicyKey( DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY, @@ -190,10 +189,8 @@ final class PolicyDefinition<V> { * {@link #GENERIC_PERSISTENT_PREFERRED_ACTIVITY}. */ static PolicyDefinition<ComponentName> PERSISTENT_PREFERRED_ACTIVITY( - IntentFilter intentFilter) { - if (intentFilter == null) { - return GENERIC_PERSISTENT_PREFERRED_ACTIVITY; - } + @NonNull IntentFilter intentFilter) { + Objects.requireNonNull(intentFilter, "intentFilter must not be null"); return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition( new IntentFilterPolicyKey( DevicePolicyIdentifiers.PERSISTENT_PREFERRED_ACTIVITY_POLICY, @@ -216,11 +213,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code packageName} will return * {@link #GENERIC_PACKAGE_UNINSTALL_BLOCKED}. */ - static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED( - String packageName) { - if (packageName == null) { - return GENERIC_PACKAGE_UNINSTALL_BLOCKED; - } + static PolicyDefinition<Boolean> PACKAGE_UNINSTALL_BLOCKED(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition( new PackagePolicyKey( DevicePolicyIdentifiers.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName)); @@ -247,10 +241,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code packageName} will return * {@link #GENERIC_APPLICATION_RESTRICTIONS}. */ - static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(String packageName) { - if (packageName == null) { - return GENERIC_APPLICATION_RESTRICTIONS; - } + static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition( new PackagePolicyKey( DevicePolicyIdentifiers.APPLICATION_RESTRICTIONS_POLICY, packageName)); @@ -293,10 +285,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code packageName} will return * {@link #GENERIC_APPLICATION_HIDDEN}. */ - static PolicyDefinition<Boolean> APPLICATION_HIDDEN(String packageName) { - if (packageName == null) { - return GENERIC_APPLICATION_HIDDEN; - } + static PolicyDefinition<Boolean> APPLICATION_HIDDEN(@NonNull String packageName) { + Objects.requireNonNull(packageName, "packageName must not be null"); return GENERIC_APPLICATION_HIDDEN.createPolicyDefinition( new PackagePolicyKey( DevicePolicyIdentifiers.APPLICATION_HIDDEN_POLICY, packageName)); @@ -319,10 +309,8 @@ final class PolicyDefinition<V> { * Passing in {@code null} for {@code accountType} will return * {@link #GENERIC_ACCOUNT_MANAGEMENT_DISABLED}. */ - static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(String accountType) { - if (accountType == null) { - return GENERIC_ACCOUNT_MANAGEMENT_DISABLED; - } + static PolicyDefinition<Boolean> ACCOUNT_MANAGEMENT_DISABLED(@NonNull String accountType) { + Objects.requireNonNull(accountType, "accountType must not be null"); return GENERIC_ACCOUNT_MANAGEMENT_DISABLED.createPolicyDefinition( new AccountTypePolicyKey( DevicePolicyIdentifiers.ACCOUNT_MANAGEMENT_DISABLED_POLICY, accountType)); @@ -708,17 +696,15 @@ final class PolicyDefinition<V> { } @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 245c43884e02..b81348969f7d 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 @@ package com.android.server.devicepolicy; 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 @@ final class PolicyState<V> { } @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 @@ final class PolicyState<V> { } break; case TAG_POLICY_DEFINITION_ENTRY: - policyDefinition = PolicyDefinition.readFromXml(parser); - if (policyDefinition == null) { - Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, " - + "PolicyDefinition is null"); + if (Flags.dontReadPolicyDefinition()) { + // Should be passed by the caller. + Objects.requireNonNull(policyDefinition); + } else { + policyDefinition = PolicyDefinition.readFromXml(parser); + if (policyDefinition == null) { + Slogf.wtf(TAG, "Error Parsing TAG_POLICY_DEFINITION_ENTRY, " + + "PolicyDefinition is null"); + } } break; diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index aee7242d4604..a27ad9a0f4e6 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -913,52 +913,100 @@ public final class InputMethodSubtypeSwitchingControllerTest { final var controller = ControllerImpl.createFrom(null /* currentInstance */, List.of(), List.of()); - assertNoAction(controller, false /* forHardware */, items); - assertNoAction(controller, true /* forHardware */, hardwareItems); + assertNextItemNoAction(controller, false /* forHardware */, items, + null /* expectedNext */); + assertNextItemNoAction(controller, true /* forHardware */, hardwareItems, + null /* expectedNext */); } - /** Verifies that a controller with a single item can't take any actions. */ + /** + * Verifies that a controller with a single item can't update the recency, and cannot switch + * away from the item, but allows switching from unknown items to the single item. + */ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP) @Test public void testSingleItemList() { final var items = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + null, true /* supportsSwitchingToNextInputMethod */); + final var unknownItems = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme", + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var hardwareItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + null, true /* supportsSwitchingToNextInputMethod */); + final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>(); + addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme", + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var controller = ControllerImpl.createFrom(null /* currentInstance */, - List.of(items.get(0)), List.of(hardwareItems.get(0))); - - assertNoAction(controller, false /* forHardware */, items); - assertNoAction(controller, true /* forHardware */, hardwareItems); + items, hardwareItems); + + assertNextItemNoAction(controller, false /* forHardware */, items, + null /* expectedNext */); + assertNextItemNoAction(controller, false /* forHardware */, unknownItems, + items.get(0)); + assertNextItemNoAction(controller, true /* forHardware */, hardwareItems, + null /* expectedNext */); + assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems, + hardwareItems.get(0)); } - /** Verifies that a controller can't take any actions for unknown items. */ + /** + * Verifies that the recency cannot be updated for unknown items, but switching from unknown + * items reaches the most recent known item. + */ @RequiresFlagsEnabled(Flags.FLAG_IME_SWITCHER_REVAMP) @Test public void testUnknownItems() { final var items = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(items, "LatinIme", "LatinIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); + + final var english = items.get(0); + final var french = items.get(1); + final var italian = items.get(2); + final var unknownItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(unknownItems, "UnknownIme", "UnknownIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var hardwareItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(hardwareItems, "HardwareIme", "HardwareIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var unknownHardwareItems = new ArrayList<ImeSubtypeListItem>(); addTestImeSubtypeListItems(unknownHardwareItems, "HardwareUnknownIme", "HardwareUnknownIme", - List.of("en", "fr"), true /* supportsSwitchingToNextInputMethod */); + List.of("en", "fr", "it"), true /* supportsSwitchingToNextInputMethod */); final var controller = ControllerImpl.createFrom(null /* currentInstance */, items, hardwareItems); - assertNoAction(controller, false /* forHardware */, unknownItems); - assertNoAction(controller, true /* forHardware */, unknownHardwareItems); + assertTrue("Recency updated for french IME", onUserAction(controller, french)); + + final var recencyItems = List.of(french, english, italian); + + assertNextItemNoAction(controller, false /* forHardware */, unknownItems, + french); + assertNextItemNoAction(controller, true /* forHardware */, unknownHardwareItems, + hardwareItems.get(0)); + + // Known items must not be able to switch to unknown items. + assertNextOrder(controller, false /* forHardware */, MODE_STATIC, items, + List.of(items)); + assertNextOrder(controller, false /* forHardware */, MODE_RECENT, recencyItems, + List.of(recencyItems)); + assertNextOrder(controller, false /* forHardware */, MODE_AUTO, true /* forward */, + recencyItems, List.of(recencyItems)); + assertNextOrder(controller, false /* forHardware */, MODE_AUTO, false /* forward */, + items.reversed(), List.of(items.reversed())); + + assertNextOrder(controller, true /* forHardware */, MODE_STATIC, hardwareItems, + List.of(hardwareItems)); + assertNextOrder(controller, true /* forHardware */, MODE_RECENT, hardwareItems, + List.of(hardwareItems)); + assertNextOrder(controller, true /* forHardware */, MODE_AUTO, hardwareItems, + List.of(hardwareItems)); } /** Verifies that the IME name does influence the comparison order. */ @@ -1199,25 +1247,26 @@ public final class InputMethodSubtypeSwitchingControllerTest { } /** - * Verifies that no next items can be found, and the recency cannot be updated for the + * Verifies that the expected next item is returned, and the recency cannot be updated for the * given items. * - * @param controller the controller to verify the items on. - * @param forHardware whether to try finding the next hardware item, or software item. - * @param items the list of items to verify. + * @param controller the controller to verify the items on. + * @param forHardware whether to try finding the next hardware item, or software item. + * @param items the list of items to verify. + * @param expectedNext the expected next item. */ - private void assertNoAction(@NonNull ControllerImpl controller, boolean forHardware, - @NonNull List<ImeSubtypeListItem> items) { + private void assertNextItemNoAction(@NonNull ControllerImpl controller, boolean forHardware, + @NonNull List<ImeSubtypeListItem> items, @Nullable ImeSubtypeListItem expectedNext) { for (var item : items) { for (int mode = MODE_STATIC; mode <= MODE_AUTO; mode++) { assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode, - false /* forward */, item, null /* expectedNext */); + false /* forward */, item, expectedNext); assertNextItem(controller, forHardware, false /* onlyCurrentIme */, mode, - true /* forward */, item, null /* expectedNext */); + true /* forward */, item, expectedNext); assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode, - false /* forward */, item, null /* expectedNext */); + false /* forward */, item, expectedNext); assertNextItem(controller, forHardware, true /* onlyCurrentIme */, mode, - true /* forward */, item, null /* expectedNext */); + true /* forward */, item, expectedNext); } assertFalse("User action shouldn't have updated the recency.", 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 b980ca05b609..30de0e8c7981 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -512,6 +512,9 @@ public final class AlarmManagerServiceTest { when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(EmptyArray.STRING); + // Initialize timestamps with arbitrary values of time + mNowElapsedTest = 12; + mNowRtcTest = 345; mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); @@ -774,6 +777,61 @@ public final class AlarmManagerServiceTest { } @Test + public void timeChangeBroadcastForward() throws Exception { + final long timeDelta = 12345; + // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms more than boot + // time clock. + mNowRtcTest += timeDelta + 1001; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + verify(mMockContext) + .sendBroadcastAsUser( + argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED), + eq(UserHandle.ALL), + isNull(), + any()); + } + + @Test + public void timeChangeBroadcastBackward() throws Exception { + final long timeDelta = 12345; + // AlarmManagerService sends the broadcast if real time clock proceeds 1000ms less than boot + // time clock. + mNowRtcTest += timeDelta - 1001; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + verify(mMockContext) + .sendBroadcastAsUser( + argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED), + eq(UserHandle.ALL), + isNull(), + any()); + } + + @Test + public void timeChangeFilterMinorAdjustment() throws Exception { + final long timeDelta = 12345; + // AlarmManagerService does not send the broadcast if real time clock proceeds within 1000ms + // than boot time clock. + mNowRtcTest += timeDelta + 1000; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + mNowRtcTest += timeDelta - 1000; + mNowElapsedTest += timeDelta; + mTestTimer.expire(TIME_CHANGED_MASK); + + verify(mMockContext, never()) + .sendBroadcastAsUser( + argThat((intent) -> intent.getAction() == Intent.ACTION_TIME_CHANGED), + any(), + any(), + any()); + } + + @Test public void testSingleAlarmExpiration() throws Exception { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); 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 0def51691efa..8753b251ac98 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.ArgumentCaptor 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 @@ class MouseKeysInterceptorTest { 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/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index dbab54b76a2e..a8e350b05f18 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -19,6 +19,8 @@ package com.android.server.am; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_FALSE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; @@ -201,7 +203,8 @@ public class UserControllerTest { doNothing().when(mInjector).systemServiceManagerOnUserStopped(anyInt()); doNothing().when(mInjector).systemServiceManagerOnUserCompletedEvent( anyInt(), anyInt()); - doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString()); + doNothing().when(mInjector).activityManagerForceStopUserPackages(anyInt(), + anyString(), anyBoolean()); doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); @@ -936,6 +939,61 @@ public class UserControllerTest { new HashSet<>(mUserController.getRunningUsersLU())); } + @Test + public void testEarlyPackageKillEnabledForUserSwitch_enabled() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + assertTrue(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_withoutDelayUserDataLocking() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + assertFalse(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_withPrevSystemUser() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + assertFalse(mUserController + .isEarlyPackageKillEnabledForUserSwitch(SYSTEM_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_stopUserOnSwitchModeOn() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ false, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + mUserController.setStopUserOnSwitch(STOP_USER_ON_SWITCH_TRUE); + + assertTrue(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + @Test + public void testEarlyPackageKillEnabledForUserSwitch_stopUserOnSwitchModeOff() { + mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, + /* maxRunningUsers= */ 4, /* delayUserDataLocking= */ true, + /* backgroundUserScheduledStopTimeSecs= */ -1); + + mUserController.setStopUserOnSwitch(STOP_USER_ON_SWITCH_FALSE); + + assertFalse(mUserController + .isEarlyPackageKillEnabledForUserSwitch(TEST_USER_ID, TEST_USER_ID1)); + } + + /** * Test that, in getRunningUsersLU, parents come after their profile, even if the profile was * started afterwards. 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 473d1dc22d7a..1074f7b4aa0a 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 class HdmiCecMessageValidatorTest { 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 @@ public class HdmiCecMessageValidatorTest { // 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 @@ public class HdmiCecMessageValidatorTest { // Invalid Recording Sequence assertMessageValidity("40:A2:14:09:12:28:4B:19:84:05:10:00").isEqualTo(ERROR_PARAMETER); // Invalid Recording Sequence - assertMessageValidity("40:A1:0C:08:15:05:04:1E:14:04:20").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:A1:0C:08:15:05:04:1E:94:04:20").isEqualTo(ERROR_PARAMETER); // Invalid external source specifier assertMessageValidity("40:A2:14:09:12:28:4B:19:10:08:10:00").isEqualTo(ERROR_PARAMETER); // Invalid External PLug diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 3d6884925098..dddab657be14 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -108,6 +108,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.CALLS_REAL_METHODS; @@ -165,6 +166,7 @@ import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.PowerSaveState; +import android.os.Process; import android.os.RemoteException; import android.os.SimpleClock; import android.os.SystemClock; @@ -197,6 +199,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent; import com.android.internal.util.test.FsUtil; @@ -2310,6 +2313,70 @@ public class NetworkPolicyManagerServiceTest { assertTrue(mService.isUidNetworkingBlocked(UID_A, false)); } + @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock + @Test + @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS) + public void testRulesNeverAppliedToCoreUids() throws Exception { + clearInvocations(mNetworkManager); + + final int coreAppId = Process.FIRST_APPLICATION_UID - 102; + final int coreUid = UserHandle.getUid(USER_ID, coreAppId); + + // Enable all restrictions and add this core uid to all allowlists. + mService.mDeviceIdleMode = true; + mService.mRestrictPower = true; + setRestrictBackground(true); + expectHasUseRestrictedNetworksPermission(coreUid, true); + enableRestrictedMode(true); + final NetworkPolicyManagerInternal internal = LocalServices.getService( + NetworkPolicyManagerInternal.class); + internal.setLowPowerStandbyActive(true); + internal.setLowPowerStandbyAllowlist(new int[]{coreUid}); + internal.onTempPowerSaveWhitelistChange(coreAppId, true, REASON_OTHER, "testing"); + + when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())) + .thenReturn(new int[]{coreAppId}); + mPowerAllowlistReceiver.onReceive(mServiceContext, null); + + // A normal uid would undergo a rule change from denied to allowed on all chains, but we + // should not request any rule change for this core uid. + verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(coreUid), anyInt()); + verify(mNetworkManager, never()).setFirewallUidRules(anyInt(), + argThat(ar -> ArrayUtils.contains(ar, coreUid)), any(int[].class)); + } + + @SuppressWarnings("GuardedBy") // For not holding mUidRulesFirstLock + @Test + @RequiresFlagsEnabled(Flags.FLAG_NEVER_APPLY_RULES_TO_CORE_UIDS) + public void testRulesNeverAppliedToUidsWithoutInternetPermission() throws Exception { + clearInvocations(mNetworkManager); + + mService.mInternetPermissionMap.clear(); + expectHasInternetPermission(UID_A, false); + + // Enable all restrictions and add this uid to all allowlists. + mService.mDeviceIdleMode = true; + mService.mRestrictPower = true; + setRestrictBackground(true); + expectHasUseRestrictedNetworksPermission(UID_A, true); + enableRestrictedMode(true); + final NetworkPolicyManagerInternal internal = LocalServices.getService( + NetworkPolicyManagerInternal.class); + internal.setLowPowerStandbyActive(true); + internal.setLowPowerStandbyAllowlist(new int[]{UID_A}); + internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing"); + + when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())) + .thenReturn(new int[]{APP_ID_A}); + mPowerAllowlistReceiver.onReceive(mServiceContext, null); + + // A normal uid would undergo a rule change from denied to allowed on all chains, but we + // should not request any rule this uid without the INTERNET permission. + verify(mNetworkManager, never()).setFirewallUidRule(anyInt(), eq(UID_A), anyInt()); + verify(mNetworkManager, never()).setFirewallUidRules(anyInt(), + argThat(ar -> ArrayUtils.contains(ar, UID_A)), any(int[].class)); + } + private boolean isUidState(int uid, int procState, int procStateSeq, int capability) { final NetworkPolicyManager.UidState uidState = mService.getUidStateForTest(uid); if (uidState == null) { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index d714db99f18f..791215695f57 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 @@ public final class UserManagerTest { // 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 @@ public final class UserManagerTest { @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 @@ public final class UserManagerTest { @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 @@ public final class UserManagerTest { } } + // 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/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java index fad10f7a7553..551808243640 100644 --- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java @@ -182,7 +182,7 @@ public final class DeviceStateProviderImplTest { + " <device-state>\n" + " <identifier>1</identifier>\n" + " <properties>\n" - + " <property>PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n" + + " <property>com.android.server.policy.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n" + " </properties>\n" + " <conditions/>\n" + " </device-state>\n" @@ -338,11 +338,9 @@ public final class DeviceStateProviderImplTest { + " <identifier>4</identifier>\n" + " <name>THERMAL_TEST</name>\n" + " <properties>\n" - + " <property>PROPERTY_EMULATED_ONLY</property>\n" - + " <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL" - + "</property>\n" - + " <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE" - + "</property>\n" + + " <property>com.android.server.policy.PROPERTY_EMULATED_ONLY</property>\n" + + " <property>com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</property>\n" + + " <property>com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE</property>\n" + " </properties>\n" + " </device-state>\n" + "</device-state-config>\n"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java index f6e1162a5ada..af7f703e9c31 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java @@ -16,7 +16,6 @@ package com.android.server.notification; -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; @@ -31,13 +30,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.app.Flags; import android.content.ComponentName; import android.content.ServiceConnection; import android.content.pm.IPackageManager; import android.net.Uri; import android.os.IInterface; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.Condition; @@ -150,57 +147,6 @@ public class ConditionProvidersTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_UI) - public void notifyConditions_appCannotUndoUserEnablement() { - ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( - mock(IInterface.class), new ComponentName("package", "cls"), 0, false, - mock(ServiceConnection.class), 33, 100); - // First, user enabled mode - Condition[] userConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION) - }; - mProviders.notifyConditions("package", msi, userConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); - - // Second, app tries to disable it, but cannot - Condition[] appConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_FALSE) - }; - mProviders.notifyConditions("package", msi, appConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); - } - - @Test - @EnableFlags(Flags.FLAG_MODES_UI) - public void notifyConditions_appCanTakeoverUserEnablement() { - ManagedServices.ManagedServiceInfo msi = mProviders.new ManagedServiceInfo( - mock(IInterface.class), new ComponentName("package", "cls"), 0, false, - mock(ServiceConnection.class), 33, 100); - // First, user enabled mode - Condition[] userConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_TRUE, SOURCE_USER_ACTION) - }; - mProviders.notifyConditions("package", msi, userConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(userConditions[0])); - - // Second, app now thinks the rule should be on due it its intelligence - Condition[] appConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_TRUE) - }; - mProviders.notifyConditions("package", msi, appConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0])); - - // Lastly, app can turn rule off when its intelligence think it should be off - appConditions = new Condition[] { - new Condition(Uri.parse("a"), "summary", STATE_FALSE) - }; - mProviders.notifyConditions("package", msi, appConditions); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(appConditions[0])); - - verifyNoMoreInteractions(mCallback); - } - - @Test public void testRemoveDefaultFromConfig() { final int userId = 0; ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); 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 14ad15e23791..643ee4aadd80 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -2458,6 +2458,74 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testBeepVolume_politeNotif_AvalancheStrategy_mixedNotif() 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); + + // Regular notification: should beep at 0% volume + NotificationRecord r = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + Mockito.reset(mRingtonePlayer); + + // Conversation notification + mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT); + NotificationRecord r2 = getConversationNotificationRecord(mId, false /* insistent */, + false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true, + true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg, + "shortcut"); + + // Should beep at 100% volume + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + // Conversation notification on a different channel + mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT); + NotificationRecord r3 = getConversationNotificationRecord(mId, false /* insistent */, + false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true, + true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg, + "shortcut"); + + // Should beep at 50% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + assertNotEquals(-1, r3.getLastAudiblyAlertedMs()); + verifyBeepVolume(0.5f); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + + // Set important conversation + mChannel.setImportantConversation(true); + r3 = getConversationNotificationRecord(mId, false /* insistent */, + false /* once */, true /* noisy */, false /* buzzy*/, false /* lights */, true, + true, false, null, Notification.GROUP_ALERT_ALL, false, mUser, mPkg, + "shortcut"); + + // important conversation should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(5)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r3.getLastAudiblyAlertedMs()); + } + + @Test public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.enableFlags(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 4bb80170f9fa..5a8de58c14ae 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -13064,6 +13064,100 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @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); 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 60c4ac777906..e70ed5f256bf 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -33,6 +33,8 @@ 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.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE; 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; @@ -524,7 +526,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.zenMode = INTERRUPTION_FILTER; rule.modified = true; rule.name = NAME; - rule.snoozing = true; + rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; @@ -546,7 +548,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule parceled = new ZenModeConfig.ZenRule(parcel); assertEquals(rule.pkg, parceled.pkg); - assertEquals(rule.snoozing, parceled.snoozing); + assertEquals(rule.getConditionOverride(), parceled.getConditionOverride()); assertEquals(rule.enabler, parceled.enabler); assertEquals(rule.component, parceled.component); assertEquals(rule.configurationActivity, parceled.configurationActivity); @@ -625,7 +627,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.zenMode = INTERRUPTION_FILTER; rule.modified = true; rule.name = NAME; - rule.snoozing = true; + rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; rule.zenDeviceEffects = new ZenDeviceEffects.Builder() @@ -662,7 +664,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.pkg, fromXml.pkg); // always resets on reboot - assertFalse(fromXml.snoozing); + assertEquals(OVERRIDE_NONE, fromXml.getConditionOverride()); //should all match original assertEquals(rule.component, fromXml.component); assertEquals(rule.configurationActivity, fromXml.configurationActivity); @@ -1115,7 +1117,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; rule.modified = true; rule.name = "name"; - rule.snoozing = false; rule.pkg = "b"; config.automaticRules.put("key", rule); 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 9af00218dda4..91eb2edeee13 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -158,7 +158,10 @@ public class ZenModeDiffTest extends UiServiceTestCase { RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS)); } - if (!(Flags.modesApi() && Flags.modesUi())) { + if (Flags.modesApi() && Flags.modesUi()) { + exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete. + } else { + exemptFields.add(RuleDiff.FIELD_CONDITION_OVERRIDE); exemptFields.add(RuleDiff.FIELD_LEGACY_SUPPRESSED_EFFECTS); } return exemptFields; @@ -339,7 +342,7 @@ public class ZenModeDiffTest extends UiServiceTestCase { rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; rule.modified = false; rule.name = "name"; - rule.snoozing = true; + rule.setConditionOverride(ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE); rule.pkg = "a"; if (android.app.Flags.modesApi()) { rule.allowManualInvocation = true; 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 776a840466c8..212e61e10448 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -51,6 +51,7 @@ 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; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import static android.service.notification.Condition.SOURCE_CONTEXT; import static android.service.notification.Condition.SOURCE_SCHEDULE; import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_FALSE; @@ -61,6 +62,9 @@ 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.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE; +import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_NONE; 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; @@ -2999,11 +3003,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertWithMessage("Failure for origin " + origin.name()) .that(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); assertWithMessage("Failure for origin " + origin.name()) - .that(mZenModeHelper.mConfig.automaticRules.get(activeRuleId).snoozing) - .isTrue(); + .that(mZenModeHelper.mConfig.automaticRules + .get(activeRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_DEACTIVATE); assertWithMessage("Failure for origin " + origin.name()) - .that(mZenModeHelper.mConfig.automaticRules.get(inactiveRuleId).snoozing) - .isFalse(); + .that(mZenModeHelper.mConfig.automaticRules + .get(inactiveRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); } } @@ -3038,16 +3044,20 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertWithMessage("Failure for origin " + origin.name()).that( mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(activeRuleId).snoozing).isFalse(); + config.automaticRules.get(activeRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(inactiveRuleId).snoozing).isFalse(); + config.automaticRules.get(inactiveRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); } else { assertWithMessage("Failure for origin " + origin.name()).that( mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(activeRuleId).snoozing).isTrue(); + config.automaticRules.get(activeRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_DEACTIVATE); assertWithMessage("Failure for origin " + origin.name()).that( - config.automaticRules.get(inactiveRuleId).snoozing).isFalse(); + config.automaticRules.get(inactiveRuleId).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); } } } @@ -4288,7 +4298,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.zenMode = INTERRUPTION_FILTER_ZR; rule.modified = true; rule.name = NAME; - rule.snoozing = true; + rule.setConditionOverride(OVERRIDE_DEACTIVATE); rule.pkg = OWNER.getPackageName(); rule.zenPolicy = POLICY; @@ -5000,7 +5010,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, ZenModeConfig.ORIGIN_SYSTEM, "", SYSTEM_UID); - assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing); + assertEquals(OVERRIDE_NONE, + mZenModeHelper.mConfig.automaticRules.get(createdId).getConditionOverride()); } @Test @@ -5713,7 +5724,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(storedRule.isAutomaticActive()).isFalse(); assertThat(storedRule.isTrueOrUnknown()).isFalse(); assertThat(storedRule.condition).isNull(); - assertThat(storedRule.snoozing).isFalse(); + assertThat(storedRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); } @@ -6039,15 +6050,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS); assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); - assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, ORIGIN_APP, "test", "test", 0); - assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride()) + .isEqualTo(OVERRIDE_DEACTIVATE); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_ALARMS); - assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).getConditionOverride()) + .isEqualTo(OVERRIDE_NONE); assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state) .isEqualTo(STATE_TRUE); } @@ -6451,6 +6465,160 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_UNKNOWN); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualActivation_appliesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + + ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + assertThat(zenRule.condition).isNull(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualActivationAndThenDeactivation_removesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + assertThat(zenRule.condition).isNull(); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRule.condition).isNull(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_manualDeactivation_appliesOverride() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, SYSTEM_UID); + ZenRule zenRuleOn = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRuleOn.isAutomaticActive()).isTrue(); + assertThat(zenRuleOn.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + assertThat(zenRuleOn.condition).isNotNull(); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + ZenRule zenRuleOff = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRuleOff.isAutomaticActive()).isFalse(); + assertThat(zenRuleOff.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + assertThat(zenRuleOff.condition).isNotNull(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_ifManualActive_appCannotDeactivateBeforeActivating() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void setAutomaticZenRuleState_ifManualInactive_appCannotReactivateBeforeDeactivating() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + ZenRule zenRule; + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "manual-off", STATE_FALSE, SOURCE_USER_ACTION), + ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-off", STATE_FALSE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isFalse(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(rule.getConditionId(), "auto-on", STATE_TRUE, SOURCE_CONTEXT), + ORIGIN_APP, CUSTOM_PKG_UID); + zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(zenRule.isAutomaticActive()).isTrue(); + assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); 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 59d557777f3b..1493253a50d4 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 com.google.common.truth.Truth.assertThat; 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.PrebakedSegment; 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 @@ public class DeviceAdapterTest { 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/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java index 9ebeaa8eb3fd..470469114dfa 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.PrimitiveSegment; 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.MockitoJUnit; 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 @@ public class VibrationScalerTest { 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 @@ public class VibrationScalerTest { 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 @@ public class VibrationScalerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) - public void testAdaptiveHapticsScale_withAdaptiveHapticsAvailable() { + @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); - 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); + 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 @@ public class VibrationScalerTest { } @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 @@ public class VibrationScalerTest { 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 @@ public class VibrationScalerTest { } @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 @@ public class VibrationScalerTest { } @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 @@ public class VibrationScalerTest { } @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 @@ public class VibrationScalerTest { } @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 72ef888aa061..6f06050f55ff 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -119,6 +119,7 @@ public class VibrationSettingsTest { USAGE_PHYSICAL_EMULATION, USAGE_RINGTONE, USAGE_TOUCH, + USAGE_IME_FEEDBACK }; @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -525,7 +526,7 @@ public class VibrationSettingsTest { setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); for (int usage : ALL_USAGES) { - if (usage == USAGE_TOUCH) { + if (usage == USAGE_TOUCH || usage == USAGE_IME_FEEDBACK) { assertVibrationIgnoredForUsage(usage, Vibration.Status.IGNORED_FOR_SETTINGS); } else { assertVibrationNotIgnoredForUsage(usage); @@ -601,14 +602,14 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() { + setKeyboardVibrationSettingsSupported(true); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM); setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/); - setKeyboardVibrationSettingsSupported(true); // Keyboard touch ignored. assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); @@ -617,7 +618,7 @@ public class VibrationSettingsTest { assertVibrationNotIgnoredForUsage(USAGE_TOUCH); assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF) .build()); @@ -625,9 +626,9 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() { + setKeyboardVibrationSettingsSupported(true); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */); - setKeyboardVibrationSettingsSupported(true); // General touch ignored. assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS); @@ -635,16 +636,16 @@ public class VibrationSettingsTest { // Keyboard touch not ignored. assertVibrationNotIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build()); } @Test - public void shouldIgnoreVibration_notSupportKeyboardVibration_ignoresKeyboardTouchVibration() { + public void shouldIgnoreVibration_notSupportKeyboardVibration_followsTouchFeedbackSettings() { + setKeyboardVibrationSettingsSupported(false); setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */); - setKeyboardVibrationSettingsSupported(false); // General touch ignored. assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS); @@ -652,7 +653,7 @@ public class VibrationSettingsTest { // Keyboard touch ignored. assertVibrationIgnoredForAttributes( new VibrationAttributes.Builder() - .setUsage(USAGE_TOUCH) + .setUsage(USAGE_IME_FEEDBACK) .setCategory(VibrationAttributes.CATEGORY_KEYBOARD) .build(), Vibration.Status.IGNORED_FOR_SETTINGS); 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 3bd56deb32f4..0fbdce4ce61a 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 @@ public class VibrationThreadTest { 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 c496bbb82630..79e272b7ec01 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.verifyZeroInteractions; 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.IBinder; 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 @@ public class VibratorControlServiceTest { 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/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java index e2524a289b7d..ddadbc41a1c0 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 @@ public class DisplayWindowSettingsTests extends WindowTestsBase { } @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/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index c53addcf220c..6fd5faf8e27d 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 @@ public class TaskDisplayAreaTests extends WindowTestsBase { 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/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index dea10b70b7b9..f0850af5fc2e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1177,6 +1177,16 @@ public class SubscriptionManager { */ public static final String SATELLITE_ESOS_SUPPORTED = SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED; + /** + * TelephonyProvider column name for satellite provisioned status. The value of this + * column is set based on whether carrier roaming NB-IOT satellite service is provisioned or + * not. By default, it's disabled. + * + * @hide + */ + public static final String IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM = + SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"USAGE_SETTING_"}, diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index ad6db2d07336..e657d7faad15 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -254,7 +254,6 @@ public final class SatelliteManager { */ public static final String KEY_PROVISION_SATELLITE_TOKENS = "provision_satellite"; - /** * The request was successfully processed. */ @@ -2643,7 +2642,7 @@ public final class SatelliteManager { @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor, - @NonNull OutcomeReceiver<List<ProvisionSubscriberId>, SatelliteException> callback) { + @NonNull OutcomeReceiver<List<SatelliteSubscriberInfo>, SatelliteException> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -2655,10 +2654,10 @@ public final class SatelliteManager { protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == SATELLITE_RESULT_SUCCESS) { if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) { - List<ProvisionSubscriberId> list = + List<SatelliteSubscriberInfo> list = resultData.getParcelableArrayList( KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN, - ProvisionSubscriberId.class); + SatelliteSubscriberInfo.class); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onResult(list))); } else { @@ -2743,9 +2742,9 @@ public final class SatelliteManager { } /** - * Deliver the list of provisioned satellite subscriber ids. + * Deliver the list of provisioned satellite subscriber infos. * - * @param list List of ProvisionSubscriberId. + * @param list The list of provisioned satellite subscriber infos. * @param executor The executor on which the callback will be called. * @param callback The callback object to which the result will be delivered. * @@ -2754,7 +2753,7 @@ public final class SatelliteManager { */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) - public void provisionSatellite(@NonNull List<ProvisionSubscriberId> list, + public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl index fe46db878909..992c9aeefb40 100644 --- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.aidl +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.aidl @@ -16,4 +16,4 @@ package android.telephony.satellite; -parcelable ProvisionSubscriberId; +parcelable SatelliteSubscriberInfo; diff --git a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java index 3e6f743e8a93..f26219bd0885 100644 --- a/telephony/java/android/telephony/satellite/ProvisionSubscriberId.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java @@ -26,7 +26,7 @@ import com.android.internal.telephony.flags.Flags; import java.util.Objects; /** - * ProvisionSubscriberId + * SatelliteSubscriberInfo * * Satellite Gateway client will use these subscriber ids to register with satellite gateway service * which identify user subscription with unique subscriber ids. These subscriber ids can be any @@ -35,7 +35,7 @@ import java.util.Objects; * @hide */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) -public final class ProvisionSubscriberId implements Parcelable { +public final class SatelliteSubscriberInfo implements Parcelable { /** provision subscriberId */ @NonNull private String mSubscriberId; @@ -49,14 +49,14 @@ public final class ProvisionSubscriberId implements Parcelable { /** * @hide */ - public ProvisionSubscriberId(@NonNull String subscriberId, @NonNull int carrierId, + public SatelliteSubscriberInfo(@NonNull String subscriberId, @NonNull int carrierId, @NonNull String niddApn) { this.mCarrierId = carrierId; this.mSubscriberId = subscriberId; this.mNiddApn = niddApn; } - private ProvisionSubscriberId(Parcel in) { + private SatelliteSubscriberInfo(Parcel in) { readFromParcel(in); } @@ -72,16 +72,16 @@ public final class ProvisionSubscriberId implements Parcelable { } @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) - public static final @android.annotation.NonNull Creator<ProvisionSubscriberId> CREATOR = - new Creator<ProvisionSubscriberId>() { + public static final @android.annotation.NonNull Creator<SatelliteSubscriberInfo> CREATOR = + new Creator<SatelliteSubscriberInfo>() { @Override - public ProvisionSubscriberId createFromParcel(Parcel in) { - return new ProvisionSubscriberId(in); + public SatelliteSubscriberInfo createFromParcel(Parcel in) { + return new SatelliteSubscriberInfo(in); } @Override - public ProvisionSubscriberId[] newArray(int size) { - return new ProvisionSubscriberId[size]; + public SatelliteSubscriberInfo[] newArray(int size) { + return new SatelliteSubscriberInfo[size]; } }; @@ -148,7 +148,7 @@ public final class ProvisionSubscriberId implements Parcelable { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - ProvisionSubscriberId that = (ProvisionSubscriberId) o; + SatelliteSubscriberInfo that = (SatelliteSubscriberInfo) o; return mSubscriberId.equals(that.mSubscriberId) && mCarrierId == that.mCarrierId && mNiddApn.equals(that.mNiddApn); } diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index b82396e710fd..e66a0824f545 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -206,77 +206,6 @@ oneway interface ISatellite { 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 b4eb15fde632..3f2fce2b9f1d 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl @@ -28,13 +28,6 @@ import android.telephony.satellite.stub.SatelliteModemState; */ 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 d8b4974f23b9..c50e469e83cb 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -142,32 +142,6 @@ public class SatelliteImplBase extends SatelliteService { } @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 @@ public class SatelliteImplBase extends SatelliteService { } /** - * 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/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl index 460de8c8113d..fb44f87ee1ee 100644 --- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriberInfo.aidl @@ -19,7 +19,7 @@ package android.telephony.satellite.stub; /** * {@hide} */ -parcelable ProvisionSubscriberId { +parcelable SatelliteSubscriberInfo { /** provision subscriberId */ String subscriberId; diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 3dbda7ae10a3..89197032dcef 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -78,7 +78,7 @@ import android.telephony.satellite.ISatelliteModemStateCallback; import android.telephony.satellite.NtnSignalStrength; import android.telephony.satellite.SatelliteCapabilities; import android.telephony.satellite.SatelliteDatagram; -import android.telephony.satellite.ProvisionSubscriberId; +import android.telephony.satellite.SatelliteSubscriberInfo; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.IBooleanConsumer; @@ -3007,16 +3007,18 @@ interface ITelephony { */ @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. @@ -3426,13 +3428,13 @@ interface ITelephony { void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result); /** - * Deliver the list of provisioned satellite subscriber ids. + * Deliver the list of provisioned satellite subscriber infos. * - * @param list List of provisioned satellite subscriber ids. + * @param list The list of provisioned satellite subscriber infos. * @param result The result receiver that returns whether deliver success or fail. * @hide */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void provisionSatellite(in List<ProvisionSubscriberId> list, in ResultReceiver result); + void provisionSatellite(in List<SatelliteSubscriberInfo> list, in ResultReceiver result); } diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp index 827ff4fbd989..ad98e47fa8f0 100644 --- a/tests/Internal/Android.bp +++ b/tests/Internal/Android.bp @@ -24,6 +24,7 @@ android_test { "flickerlib-parsers", "perfetto_trace_java_protos", "flickerlib-trace_processor_shell", + "ravenwood-junit", ], java_resource_dirs: ["res"], certificate: "platform", @@ -39,6 +40,7 @@ android_ravenwood_test { "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 d0bb8e3745bc..38a22f2fc2f3 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 @@ package com.android.internal.graphics; 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 000000000000..e3ec62d5b5a6 --- /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/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java index 65a3436a4c5e..fb63422cdf9f 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 @@ package com.android.internal.util; 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 androidx.test.filters.SmallTest; 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 @@ import java.time.Instant; @RunWith(JUnit4.class) public class ParcellingTests { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Parcel mParcel = Parcel.obtain(); @Test diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 3f9016ba4852..f43cf521edf5 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -113,6 +113,7 @@ cc_library_host_static { "io/ZipArchive.cpp", "link/AutoVersioner.cpp", "link/FeatureFlagsFilter.cpp", + "link/FlagDisabledResourceRemover.cpp", "link/ManifestFixer.cpp", "link/NoDefaultResourceRemover.cpp", "link/PrivateAttributeMover.cpp", @@ -189,6 +190,8 @@ cc_test_host { "integration-tests/CommandTests/**/*", "integration-tests/ConvertTest/**/*", "integration-tests/DumpTest/**/*", + ":resource-flagging-test-app-apk", + ":resource-flagging-test-app-r-java", ], } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 9444dd968f5f..1c85e9ff231b 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -690,9 +690,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, resource_format = item_iter->second.format; } - // Don't bother parsing the item if it is behind a disabled flag - if (out_resource->flag_status != FlagStatus::Disabled && - !ParseItem(parser, out_resource, resource_format)) { + if (!ParseItem(parser, out_resource, resource_format)) { return false; } return true; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 2e6ad13d99de..b59b16574c42 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -69,13 +69,8 @@ class ResourceParserTest : public ::testing::Test { return TestParse(str, ConfigDescription{}); } - ::testing::AssertionResult TestParse(StringPiece str, ResourceParserOptions parserOptions) { - return TestParse(str, ConfigDescription{}, parserOptions); - } - - ::testing::AssertionResult TestParse( - StringPiece str, const ConfigDescription& config, - ResourceParserOptions parserOptions = ResourceParserOptions()) { + ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) { + ResourceParserOptions parserOptions; ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config, parserOptions); @@ -247,19 +242,6 @@ TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) { EXPECT_FALSE(TestParse(R"(<string name="foo4" translatable="yes">Translate</string>)")); } -TEST_F(ResourceParserTest, ParseStringBehindDisabledFlag) { - FeatureFlagProperties flag_properties(true, false); - ResourceParserOptions options; - options.feature_flag_values = {{"falseFlag", flag_properties}}; - ASSERT_TRUE(TestParse( - R"(<string name="foo" android:featureFlag="falseFlag" - xmlns:android="http://schemas.android.com/apk/res/android">foo</string>)", - options)); - - String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_THAT(str, IsNull()); -} - TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 642a5618b6ad..56f52885b36d 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -57,6 +57,7 @@ #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/FeatureFlagsFilter.h" +#include "link/FlagDisabledResourceRemover.h" #include "link/Linkers.h" #include "link/ManifestFixer.h" #include "link/NoDefaultResourceRemover.h" @@ -1840,11 +1841,57 @@ class Linker { return validate(attr->value); } + class FlagDisabledStringVisitor : public DescendingValueVisitor { + public: + using DescendingValueVisitor::Visit; + + explicit FlagDisabledStringVisitor(android::StringPool& string_pool) + : string_pool_(string_pool) { + } + + void Visit(RawString* value) override { + value->value = string_pool_.MakeRef(""); + } + + void Visit(String* value) override { + value->value = string_pool_.MakeRef(""); + } + + void Visit(StyledString* value) override { + value->value = string_pool_.MakeRef(android::StyleString{{""}, {}}); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FlagDisabledStringVisitor); + android::StringPool& string_pool_; + }; + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { TRACE_CALL(); + + FlagDisabledStringVisitor visitor(table->string_pool); + + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + for (auto& config_value : entry->values) { + if (config_value->flag_status == FlagStatus::Disabled) { + config_value->value->Accept(&visitor); + } + } + } + } + } + + if (!FlagDisabledResourceRemover{}.Consume(context_, table)) { + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed removing resources behind disabled flags"); + return 1; + } + const bool keep_raw_values = (context_->GetPackageType() == PackageType::kStaticLib) || options_.keep_raw_values; bool result = FlattenXml(context_, *manifest, kAndroidManifestPath, keep_raw_values, @@ -2331,6 +2378,12 @@ class Linker { return 1; }; + if (options_.generate_java_class_path || options_.generate_text_symbols_path) { + if (!GenerateJavaClasses()) { + return 1; + } + } + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) { return 1; } @@ -2339,12 +2392,6 @@ class Linker { return 1; } - if (options_.generate_java_class_path || options_.generate_text_symbols_path) { - if (!GenerateJavaClasses()) { - return 1; - } - } - if (!WriteProguardFile(options_.generate_proguard_rules_path, proguard_keep_set)) { return 1; } diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp new file mode 100644 index 000000000000..5932271d4d28 --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp @@ -0,0 +1,81 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_android_resources", +} + +genrule { + name: "resource-flagging-test-app-compile", + tools: ["aapt2"], + srcs: [ + "res/values/bools.xml", + "res/values/bools2.xml", + "res/values/strings.xml", + ], + out: [ + "values_bools.arsc.flat", + "values_bools2.arsc.flat", + "values_strings.arsc.flat", + ], + cmd: "$(location aapt2) compile $(in) -o $(genDir) " + + "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", +} + +genrule { + name: "resource-flagging-test-app-apk", + tools: ["aapt2"], + // The first input file in the list must be the manifest + srcs: [ + "AndroidManifest.xml", + ":resource-flagging-test-app-compile", + ], + out: [ + "resapp.apk", + ], + cmd: "$(location aapt2) link -o $(out) --manifest $(in)", +} + +genrule { + name: "resource-flagging-test-app-r-java", + tools: ["aapt2"], + // The first input file in the list must be the manifest + srcs: [ + "AndroidManifest.xml", + ":resource-flagging-test-app-compile", + ], + out: [ + "resource-flagging-java/com/android/intenal/flaggedresources/R.java", + ], + cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in)", +} + +java_genrule { + name: "resource-flagging-test-app-apk-as-resource", + srcs: [ + ":resource-flagging-test-app-apk", + ], + out: ["apks_as_resources.res.zip"], + tools: ["soong_zip"], + + cmd: "mkdir -p $(genDir)/res/raw && " + + "cp $(in) $(genDir)/res/raw/$$(basename $(in)) && " + + "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", +} diff --git a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml index d6cdeb7b5231..d6cdeb7b5231 100644 --- a/core/tests/resourceflaggingtests/TestAppAndroidManifest.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/AndroidManifest.xml diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml index 8d0146511d1d..3e094fbd669c 100644 --- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml @@ -7,4 +7,6 @@ <bool name="res2" android:featureFlag="test.package.trueFlag">true</bool> <bool name="res3">false</bool> + + <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool> </resources>
\ No newline at end of file diff --git a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml index e7563aa0fbdd..e7563aa0fbdd 100644 --- a/core/tests/resourceflaggingtests/flagged_resources_res/values/bools2.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml new file mode 100644 index 000000000000..5c0fca16fe39 --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <string name="str">plain string</string> + + <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp new file mode 100644 index 000000000000..e3289e2a173a --- /dev/null +++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/FlagDisabledResourceRemover.h" + +#include <algorithm> + +#include "ResourceTable.h" + +using android::ConfigDescription; + +namespace aapt { + +static bool KeepResourceEntry(const std::unique_ptr<ResourceEntry>& entry) { + if (entry->values.empty()) { + return true; + } + const auto end_iter = entry->values.end(); + const auto remove_iter = + std::stable_partition(entry->values.begin(), end_iter, + [](const std::unique_ptr<ResourceConfigValue>& value) -> bool { + return value->flag_status != FlagStatus::Disabled; + }); + + bool keep = remove_iter != entry->values.begin(); + + entry->values.erase(remove_iter, end_iter); + return keep; +} + +bool FlagDisabledResourceRemover::Consume(IAaptContext* context, ResourceTable* table) { + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + const auto end_iter = type->entries.end(); + const auto remove_iter = std::stable_partition( + type->entries.begin(), end_iter, [](const std::unique_ptr<ResourceEntry>& entry) -> bool { + return KeepResourceEntry(entry); + }); + + type->entries.erase(remove_iter, end_iter); + } + } + return true; +} + +} // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.h b/tools/aapt2/link/FlagDisabledResourceRemover.h new file mode 100644 index 000000000000..2db2cb44c4cc --- /dev/null +++ b/tools/aapt2/link/FlagDisabledResourceRemover.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "android-base/macros.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +// Removes any resource that are behind disabled flags. +class FlagDisabledResourceRemover : public IResourceTableConsumer { + public: + FlagDisabledResourceRemover() = default; + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(FlagDisabledResourceRemover); +}; + +} // namespace aapt diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp new file mode 100644 index 000000000000..c901b5866279 --- /dev/null +++ b/tools/aapt2/link/FlaggedResources_test.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LoadedApk.h" +#include "cmd/Dump.h" +#include "io/StringStream.h" +#include "test/Test.h" +#include "text/Printer.h" + +using ::aapt::io::StringOutputStream; +using ::aapt::text::Printer; +using testing::Eq; +using testing::Ne; + +namespace aapt { + +using FlaggedResourcesTest = CommandTestFixture; + +static android::NoOpDiagnostics noop_diag; + +void DumpStringPoolToString(LoadedApk* loaded_apk, std::string* output) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpStringsCommand command(&printer, &noop_diag); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +void DumpResourceTableToString(LoadedApk* loaded_apk, std::string* output) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpTableCommand command(&printer, &noop_diag); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +void DumpChunksToString(LoadedApk* loaded_apk, std::string* output) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpChunks command(&printer, &noop_diag); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +TEST_F(FlaggedResourcesTest, DisabledStringRemovedFromPool) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpStringPoolToString(loaded_apk.get(), &output); + + std::string excluded = "DONTFIND"; + ASSERT_EQ(output.find(excluded), std::string::npos); +} + +TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpResourceTableToString(loaded_apk.get(), &output); +} + +TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpChunksToString(loaded_apk.get(), &output); + + ASSERT_EQ(output.find("res4"), std::string::npos); + ASSERT_EQ(output.find("str1"), std::string::npos); +} + +TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) { + auto r_path = file::BuildPath({android::base::GetExecutableDirectory(), "resource-flagging-java", + "com", "android", "intenal", "flaggedresources", "R.java"}); + std::string r_contents; + ::android::base::ReadFileToString(r_path, &r_contents); + + ASSERT_NE(r_contents.find("public static final int res4"), std::string::npos); + ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos); +} + +} // namespace aapt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 5dde265c79fb..36bfbefdb086 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -58,17 +58,19 @@ class HostStubGen(val options: HostStubGenOptions) { // 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. @@ -91,16 +93,18 @@ class HostStubGen(val options: HostStubGenOptions) { // 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") } } @@ -221,47 +225,48 @@ class HostStubGen(val options: HostStubGenOptions) { 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() - - val packageRedirector = PackageRedirectRemapper(options.packageRedirects) + log.iTime("Transforming jar") { + val packageRedirector = PackageRedirectRemapper(options.packageRedirects) - var itemIndex = 0 - var numItemsProcessed = 0 - var numItems = -1 // == Unknown + 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 -> - - 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, + 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++ + 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). %d / %d item(s) processed.", - (end - start) / 1000.0, numItemsProcessed, numItems) } private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt index 18065ba56c52..ee4a06fb983d 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 @@ class HostStubGenLogger { 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/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt index 92906a75b93a..2607df63f146 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 @@ class ClassNodes { * Load all the classes, without code. */ fun loadClassStructures(inJar: String): ClassNodes { - log.i("Reading class structure from $inJar ...") - val start = System.currentTimeMillis() - - val allClasses = ClassNodes() - - log.withIndent { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - 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()) + log.iTime("Reading class structure from $inJar") { + val allClasses = ClassNodes() + + log.withIndent { + ZipFile(inJar).use { inZip -> + val inEntries = inZip.entries() + + 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()) + } } } } } } + 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 |